Files
JetCore/jetc/build-shared.sh
T
Brad Arant 54b6e522f9 build-shared.sh: rebuild on stale objects, skip needless re-archiving, link atomically
Three correctness fixes to the --shared build path:

- Only skipped rebuilding a .pic.o when it existed at all, never checking
  whether the source .cpp was newer - confirmed live: Tag.pic.o (and 6
  other JetCore objects) sat uncompiled since 2026-08-25 while Tag.cpp
  itself picked up same-day fixes, so every .jtc silently ran yesterday's
  JetCore code.
- Only re-archive libCoreUtils_pic.a when a member object actually changed,
  instead of unconditionally on every build.
- Link to a temp file in the same directory, then rename() it into place,
  instead of linking directly onto the live .jtc path. A running APIServer
  may already have that exact path dlopen()ed (hot-reload watches this
  directory) - overwriting it in place corrupts the live mapping's code
  pages out from under the process, causing a dlclose() SIGSEGV the next
  time it reloads. rename() never touches the old inode, so anything with
  it still mapped keeps seeing the unmodified original.
2026-09-01 00:04:25 +00:00

114 lines
5.0 KiB
Bash
Executable File

#!/bin/bash
# Compiles a .jet file into a shared object (conventionally named .jtc -
# "jet compiled" - to mark it as an APIServer-loadable content module, not
# a generic .so) exporting a stable, dlsym()-able render entry point -
# `jetc_render_<basename>(jet::Global &global, coreutils::MString &out)`
# - for a long-running server to load and call directly instead of
# spawning a jet-2.0 process per request.
#
# Usage: ./build-shared.sh <input.jet> <output.jtc>
#
# The host server links/dlopen()s the .jtc and calls the exported symbol
# (see the "entry symbol:" line jetc prints) with its own jet::Global -
# this script does not decide that lifecycle, only builds the module.
# coreutils::Exception propagates out of the call uncaught; the host
# decides how to handle a rendering failure (log it, 500, etc.).
set -e
JC="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CU="$JC/../CoreUtils"
HERE="$JC/jetc"
if [ $# -ne 2 ]; then
echo "usage: $0 <input.jet> <output.jtc>"
exit 1
fi
INPUT="$1"
OUTPUT="$2"
STEM="$(basename "$OUTPUT" .jtc)"
if [ ! -x "$HERE/jetc" ]; then
echo "Building jetc..."
g++ -g -std=c++23 -o "$HERE/jetc" "$HERE/jetc.cpp" "$HERE/JetParser.cpp" "$HERE/JetCodegen.cpp"
fi
echo "Compiling $INPUT -> $STEM.compiled.cpp (shared mode)"
"$HERE/jetc" --shared "$INPUT" "$HERE/$STEM.compiled.cpp"
echo "Building JetCore runtime objects as position-independent code..."
# Rebuild whenever the SOURCE is newer than the cached object, not just
# when the object is missing - a pure existence check silently keeps
# serving a stale compile forever once the .o exists once, even after the
# .cpp changes. Confirmed this bit for real: Tag.pic.o (and 6 other
# JetCore objects) sat uncompiled since 2026-08-25 while Tag.cpp itself
# picked up same-day fixes (the <try> tag dispatch among them) - every
# .jtc built off the stale cache silently ran yesterday's JetCore code,
# with <try> falling through as unrecognized literal text at runtime.
list=""
for f in "$JC"/*.cpp; do
base=$(basename "$f")
if [ "$base" = "jet-2.0.cpp" ]; then continue; fi
obj="$HERE/${base%.cpp}.pic.o"
if [ ! -f "$obj" ] || [ "$f" -nt "$obj" ]; then
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$f" -o "$obj" &
fi
list="$list $obj"
done
# libCoreUtils.a (built by CoreUtils/compile) is not position-independent -
# ld refuses to link non-PIC static-library code into a shared object
# ("relocation ... can not be used when making a shared object"). Rather
# than touching CoreUtils's own build (its .a is fine and used as-is by
# jet-2.0 and everything else), build a PIC copy of its objects here and
# archive them - archived the same way libCoreUtils.a itself is, so the
# linker only pulls in objects something actually references, same as a
# normal -lCoreUtils link. This matters: force-linking every CoreUtils
# .cpp directly (as JetCore's own objects are, below) surfaces an
# unrelated, pre-existing incomplete class (IMFMessage - a known
# partially-migrated class, see project memory) as an unresolved vtable
# at dlopen() time, even though nothing here actually uses it - a normal
# archive link never pulls IMFMessage.o in at all, so it never hits this.
cu_pic_list=""
cu_rebuilt=false
for f in "$CU"/*.cpp; do
base=$(basename "$f")
obj="$HERE/cu_${base%.cpp}.pic.o"
if [ ! -f "$obj" ] || [ "$f" -nt "$obj" ]; then
g++ -g -fPIC -c -std=c++23 -I"$CU" "$f" -o "$obj" &
cu_rebuilt=true
fi
cu_pic_list="$cu_pic_list $obj"
done
wait
# Only re-archive when something actually changed - ar rcs on an unchanged
# member set is harmless but pointless work every single build otherwise.
if [ "$cu_rebuilt" = true ] || [ ! -f "$HERE/libCoreUtils_pic.a" ]; then
ar rcs "$HERE/libCoreUtils_pic.a" $cu_pic_list
fi
g++ -g -fPIC -c -std=c++23 -I"$CU" -I"$JC" "$HERE/$STEM.compiled.cpp" -o "$HERE/$STEM.compiled.pic.o"
echo "Linking $OUTPUT..."
# Link to a temp file in the SAME directory as $OUTPUT, then rename() it
# into place, rather than linking directly onto $OUTPUT - a live
# APIServer instance may already have this exact path dlopen()ed (hot-
# reload watches this directory). g++/ld opens $OUTPUT with O_TRUNC and
# writes the new object into that same inode, which is also the one
# backing the running server's mmap()ed view of the OLD module -
# corrupting its code pages out from under it mid-flight. Confirmed as
# the real cause of a live, reproducible dlclose() SIGSEGV (garbage PC
# inside _dl_call_fini): the server's own unload-before-reload path
# calls dlclose() on the now-corrupted mapping shortly after a build
# like this one lands. rename() is atomic and, same filesystem, never
# touches the old inode - any process with it still mapped keeps seeing
# the unmodified original until it unmaps, and the watcher's own
# dlclose()+dlopen() cycle (triggered by the CLOSE_WRITE this rename
# still generates) picks up the new file cleanly on its next load.
TMPOUT="$(dirname "$OUTPUT")/.$STEM.jtc.tmp.$$"
g++ -shared -fPIC -o "$TMPOUT" "$HERE/$STEM.compiled.pic.o" $list -std=c++23 \
-L"$HERE" -lCoreUtils_pic -lmysqlclient -lcrypto
mv -f "$TMPOUT" "$OUTPUT"
echo "Built $OUTPUT"