Skill Tracer
Skills › gstack › gstack › build-cso.sh

build-cso.sh

437 lines · scripts/build-cso.sh at 3f79d81

1#!/usr/bin/env bash
2set -eu
3GSTACK_BUN_CONFIG=/dev/null; case "${OSTYPE:-}" in msys*|cygwin*) if command -v cygpath >/dev/null 2>&1; then GSTACK_BUN_CONFIG=NUL; fi ;; esac
4
5CSO_BUILD_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
6cd "$CSO_BUILD_ROOT"
7umask 077
8BUN_CMD="${BUN_CMD:-bun}"
9CSO_CC="${CSO_CC:-cc}"
10CSO_EXE=""
11case "$(uname -s)" in
12 MINGW*|MSYS*|CYGWIN*|Windows_NT) CSO_EXE=".exe" ;;
13esac
15CSO_FINAL_CORE="$CSO_BUILD_ROOT/bin/gstack-cso-core$CSO_EXE"
16CSO_FINAL_LAUNCHER="$CSO_BUILD_ROOT/bin/gstack-cso-launcher$CSO_EXE"
17CSO_FINAL_WATCHDOG="$CSO_BUILD_ROOT/bin/gstack-cso-watchdog"
18CSO_FINAL_GENERATION="$CSO_BUILD_ROOT/bin/.gstack-cso-generation"
19# Build-result record read by setup's summary and gstack-doctor (#3071).
20CSO_RESULT_FILE="$CSO_BUILD_ROOT/bin/.gstack-cso-build-result"
21CSO_RESULT_RECORDING=0
22CSO_RESULT_STAGE=build
23CSO_REVISION=""
24CSO_PREVIOUS_INSTALLED=""
25CSO_STAGE=""
26CSO_PUBLISHING=0
27CSO_COMMITTED=0
28CSO_OLD_COHERENT=0
29CSO_OLD_LAUNCHER_MOVED=0
30CSO_OLD_CORE_MOVED=0
31CSO_OLD_WATCHDOG_MOVED=0
32CSO_OLD_GENERATION_MOVED=0
33CSO_NEW_LAUNCHER_INSTALLED=0
34CSO_NEW_CORE_INSTALLED=0
35CSO_NEW_WATCHDOG_INSTALLED=0
36CSO_NEW_GENERATION_INSTALLED=0
37CSO_EVALUATION_UNIT=""
39cso_checkpoint() {
40 [ "${GSTACK_CSO_BUILD_TESTING:-0}" = 1 ] || return 0
41 if [ "${GSTACK_CSO_BUILD_TEST_FAIL_AFTER:-}" = "$1" ]; then
42 echo "Injected CSO build failure after $1" >&2
43 exit 86
44 fi
45 if [ "${GSTACK_CSO_BUILD_TEST_KILL_AFTER:-}" = "$1" ]; then
46 kill -KILL "$$"
47 fi
48 if [ "${GSTACK_CSO_BUILD_TEST_REMOVE_LOCKER_AFTER:-}" = "$1" ]; then
49 rm -f "$CSO_STAGE_LOCKER"
50 fi
53cso_record_field() { sed -n "s/^$1=//p" "$CSO_RESULT_FILE" 2>/dev/null | head -1; }
55# One record per run: result (ok/failed), stage (build/publish), reason, this
56# checkout's revision, the revision of the CSO generation still installed, and
57# whether that launcher is usable. A SIGKILL leaves the last "interrupted" entry.
58cso_write_result() {
59 launcher=no
60 if cso_valid_artifact "$CSO_FINAL_LAUNCHER" && cso_valid_artifact "$CSO_FINAL_CORE"; then launcher=yes; fi
61 installed=""
62 if [ "$1" = ok ]; then installed=$CSO_REVISION
63 elif [ "$launcher" = yes ]; then installed=${CSO_PREVIOUS_INSTALLED:-an earlier build}; fi
64 # Written in place: publication's signal tests count on mv moving artifacts only.
65 printf 'result=%s\nstage=%s\nreason=%s\nrevision=%s\ninstalled=%s\nlauncher=%s\ndiagnostic=%s\n' \
66 "$1" "$2" "$3" "$CSO_REVISION" "$installed" "$launcher" "${GSTACK_CSO_BUILD_LOG:-the scripts/build-cso.sh output}" > "$CSO_RESULT_FILE" || true
69cso_restore_previous() {
70 rollback_ok=1
71 # A public launcher is removed before any supporting artifact is restored.
72 if [ "$CSO_NEW_LAUNCHER_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_LAUNCHER" || rollback_ok=0; fi
73 if [ "$CSO_NEW_CORE_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_CORE" || rollback_ok=0; fi
74 if [ -z "$CSO_EXE" ] && [ "$CSO_NEW_WATCHDOG_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_WATCHDOG" || rollback_ok=0; fi
75 if [ "$CSO_NEW_GENERATION_INSTALLED" -eq 1 ];then rm -f "$CSO_FINAL_GENERATION"||rollback_ok=0;fi
76 if [ "$CSO_OLD_CORE_MOVED" -eq 1 ]; then
77 if [ "${GSTACK_CSO_BUILD_TESTING:-0}" = 1 ] && [ "${GSTACK_CSO_BUILD_TEST_FAIL_RESTORE:-}" = core ];then rollback_ok=0
78 elif cso_present "$CSO_STAGE/previous/core";then cso_move "$CSO_STAGE/previous/core" "$CSO_FINAL_CORE" || rollback_ok=0
79 elif ! cso_valid_artifact "$CSO_FINAL_CORE";then rollback_ok=0;fi
80 fi
81 if [ -z "$CSO_EXE" ] && [ "$CSO_OLD_WATCHDOG_MOVED" -eq 1 ]; then
82 if cso_present "$CSO_STAGE/previous/watchdog";then cso_move "$CSO_STAGE/previous/watchdog" "$CSO_FINAL_WATCHDOG" || rollback_ok=0
83 elif ! cso_valid_artifact "$CSO_FINAL_WATCHDOG";then rollback_ok=0;fi
84 fi
85 if [ "$CSO_OLD_GENERATION_MOVED" -eq 1 ];then
86 if cso_present "$CSO_STAGE/previous/generation";then cso_move "$CSO_STAGE/previous/generation" "$CSO_FINAL_GENERATION"||rollback_ok=0
87 elif ! cso_valid_generation "$CSO_FINAL_GENERATION";then rollback_ok=0;fi
88 fi
89 # Restore the old launcher last, and only beside the complete old support set.
90 if [ "$CSO_OLD_LAUNCHER_MOVED" -eq 1 ]; then
91 if [ "$rollback_ok" -eq 1 ] && [ "$CSO_OLD_COHERENT" -eq 1 ]; then
92 if cso_present "$CSO_STAGE/previous/launcher";then cso_move "$CSO_STAGE/previous/launcher" "$CSO_FINAL_LAUNCHER" || rollback_ok=0
93 elif ! cso_valid_artifact "$CSO_FINAL_LAUNCHER";then rollback_ok=0;fi
94 else
95 rollback_ok=0
96 fi
97 fi
98 if [ "$rollback_ok" -ne 1 ]; then
99 rm -f "$CSO_FINAL_LAUNCHER"
100 : > "$CSO_STAGE/.retain-recovery"
101 echo "CSO build rollback was incomplete; the public launcher is withheld and recovery files remain in $CSO_STAGE" >&2
102 return 1
103 fi
104 return 0
107cso_cleanup() {
108 status=$1
109 trap - EXIT
110 trap '' HUP INT TERM
111 set +e
112 cleanup_stage=1
113 if [ "$CSO_PUBLISHING" -eq 1 ] && [ "$CSO_COMMITTED" -ne 1 ]; then
114 if ! cso_restore_previous; then status=1; cleanup_stage=0; fi
115 fi
116 if [ "$cleanup_stage" -eq 1 ] && [ -n "$CSO_STAGE" ] && [ ! -f "$CSO_STAGE/.retain-recovery" ]; then rm -rf "$CSO_STAGE"; fi
117 if [ -n "$CSO_EVALUATION_UNIT" ]; then rm -rf "$CSO_EVALUATION_UNIT"; fi
118 if [ "$CSO_RESULT_RECORDING" -eq 1 ]; then
119 if [ "$status" -eq 0 ] && [ "$CSO_COMMITTED" -eq 1 ]; then cso_write_result ok publish committed
120 else cso_write_result failed "$CSO_RESULT_STAGE" "exit $status"; fi
121 fi
122 exit "$status"
124trap 'cso_cleanup $?' EXIT
125trap 'exit 129' HUP
126trap 'exit 130' INT
127trap 'exit 143' TERM
129cso_valid_artifact() { [ -f "$1" ] && [ ! -L "$1" ] && [ -x "$1" ]; }
130cso_present() { [ -e "$1" ] || [ -L "$1" ]; }
131cso_valid_generation() {
132 [ -f "$1" ]&&[ ! -L "$1" ]||return 1
133 generation_size="$(wc -c < "$1" 2>/dev/null||true)"
134 generation_size="${generation_size//[[:space:]]/}"
135 [ "$generation_size" = 65 ]||return 1
136 generation="$(cat "$1" 2>/dev/null)";[ "${#generation}" -eq 64 ]||return 1
137 case "$generation" in *[!a-f0-9]*) return 1;;esac
139cso_move() {
140 move_attempts=0
141 while ! mv "$1" "$2";do
142 move_attempts=$((move_attempts+1))
143 if [ -z "$CSO_EXE" ] || [ "$move_attempts" -ge 50 ];then return 1;fi
144 sleep .02
145 done
148cso_sign_macos_artifact() {
149 artifact=$1
150 hardened=$2
151 if [ "$(uname -m)" = arm64 ]; then
152 sig_end="$(otool -l "$artifact" 2>/dev/null | awk '/LC_CODE_SIGNATURE/{f=1} f&&/dataoff/{o=$2} f&&/datasize/{print o+$2; exit}')"
153 file_size="$(stat -f%z "$artifact" 2>/dev/null || true)"
154 if [ -n "$sig_end" ] && [ -n "$file_size" ] && [ "$sig_end" -gt 0 ] 2>/dev/null && [ "$sig_end" -lt "$file_size" ] 2>/dev/null; then
155 truncated="$CSO_STAGE/.codesign-truncated"
156 head -c "$sig_end" "$artifact" > "$truncated"
157 cat "$truncated" > "$artifact"
158 rm -f "$truncated"
159 chmod +x "$artifact"
160 fi
161 fi
162 codesign --remove-signature "$artifact" 2>/dev/null || true
163 if [ "$hardened" -eq 1 ]; then codesign --force --sign - --options runtime "$artifact"
164 else codesign --force --sign - "$artifact"; fi
165 codesign --verify --strict "$artifact"
166 if [ "$hardened" -eq 1 ]; then codesign -d --verbose=4 "$artifact" 2>&1 | grep -q 'runtime'; fi
169cso_sha256() {
170 if [ -x /usr/bin/sha256sum ];then digest="$(/usr/bin/sha256sum "$1")";digest=${digest%% *}
171 elif [ -x /usr/bin/shasum ];then digest="$(/usr/bin/shasum -a 256 "$1")";digest=${digest%% *}
172 else echo 'CSO build requires sha256sum or shasum to bind the native launcher to its core.' >&2;return 1;fi
173 case "$digest" in *[!a-f0-9]*|'') echo 'CSO build received an invalid core SHA-256 digest.' >&2;return 1;;esac
174 [ "${#digest}" -eq 64 ] || { echo 'CSO build received an invalid core SHA-256 digest.' >&2;return 1; }
175 printf '%s\n' "$digest"
178# Bun's compile-time switches stop configuration discovery in the audited
179# working directory before the helper can apply its own policy.
180cso_compile_bun() {
181 "$BUN_CMD" build --compile \
182 --no-compile-autoload-dotenv \
183 --no-compile-autoload-bunfig \
184 --no-compile-autoload-tsconfig \
185 --no-compile-autoload-package-json \
186 "$1" --outfile "$2"
187 chmod +x "$2"
189cso_compile_watchdog() {
190 "$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra \
191 lib/cso/watchdog.c -o "$1"
192 chmod +x "$1"
194cso_compile_posix_launcher() {
195 CSO_LAUNCHER_FLAGS=""
196 case "$(uname -s)" in
197 Linux) CSO_LAUNCHER_FLAGS="-static" ;;
198 # Local ad-hoc signatures do not distinguish the launcher from another
199 # ad-hoc dylib. This section makes dyld prune DYLD_* before constructors.
200 Darwin) CSO_LAUNCHER_FLAGS="-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null" ;;
201 esac
202 # shellcheck disable=SC2086 -- the optional platform flags are fixed literals.
203 "$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra $CSO_LAUNCHER_FLAGS \
204 "-DGSTACK_CSO_CORE_SHA256=\"$2\"" lib/cso/launcher.c -o "$1"
207# An evaluation candidate embeds a cso-eval-* runtime catalog in a private
208# five-artifact producer unit outside this checkout. It never touches bin/, and
209# its .gstack-cso-evaluation manifest marks it evaluation-only for setup.
210cso_build_evaluation_candidate() {
211 [ "$#" -eq 4 ] && [ "$1" = --evaluation-candidate ] && [ "$3" = --output ] || {
212 echo 'Usage: build-cso.sh --evaluation-candidate <cso-eval-catalog.json> --output <new-directory>' >&2;return 64; }
213 [ -z "$CSO_EXE" ] || { echo 'CSO evaluation candidates are built only on Linux and macOS.' >&2;return 69; }
214 command -v "$CSO_CC" >/dev/null 2>&1 || { echo 'CSO build requires a C compiler (cc/clang/gcc) for the trusted launcher and watchdog.' >&2;return 1; }
215 candidate_catalog=$2
216 [ -f "$candidate_catalog" ] && [ ! -L "$candidate_catalog" ] || { echo 'CSO evaluation catalog must be one regular file.' >&2;return 66; }
217 candidate_parent="$(cd "$(dirname "$4")" 2>/dev/null && pwd -P)" || { echo 'CSO evaluation output parent must be an existing directory.' >&2;return 73; }
218 candidate_output="$candidate_parent/$(basename "$4")"
219 if cso_present "$candidate_output";then echo 'CSO evaluation output must be a new directory.' >&2;return 73;fi
220 case "$candidate_output/" in "$CSO_BUILD_ROOT"/*) echo 'CSO evaluation output must be outside the gstack checkout, where setup and distribution never look.' >&2;return 73;;esac
221 candidate_revision="$(grep -o '"revision": *"cso-eval-[0-9]\{1,\}-[a-f0-9]\{12\}"' "$candidate_catalog" | head -n 1 | sed 's/.*"\(cso-eval-[^"]*\)"$/\1/' || true)"
222 [ -n "$candidate_revision" ] || { echo 'CSO evaluation candidates require a cso-eval-<runId>-<sha12> catalog revision.' >&2;return 65; }
224 CSO_STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gstack-cso-evaluation.XXXXXX")"
225 CSO_EVALUATION_UNIT="$(mktemp -d "$candidate_parent/.gstack-cso-evaluation.XXXXXX")"
226 source_root="$CSO_STAGE/source"
227 mkdir -m 700 "$source_root" "$source_root/test"
228 cp -R lib scripts "$source_root/"
229 cp -R test/helpers "$source_root/test/"
230 cp "$candidate_catalog" "$source_root/lib/cso/runtime-catalog.json"
231 (cd "$source_root" && "$BUN_CMD" --no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}" scripts/cso-runtime-promotion.ts check-evaluation lib/cso/runtime-catalog.json)
232 (cd "$source_root" && cso_compile_bun lib/cso/cli.ts "$CSO_EVALUATION_UNIT/gstack-cso-core")
233 (cd "$source_root" && cso_compile_bun scripts/cso-eval-producer.ts "$CSO_EVALUATION_UNIT/cso-eval-producer")
234 cso_compile_watchdog "$CSO_EVALUATION_UNIT/gstack-cso-watchdog"
235 if [ "$(uname -s)" = Darwin ]; then
236 cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-core" 0
237 cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-watchdog" 0
238 fi
239 candidate_core_sha256="$(cso_sha256 "$CSO_EVALUATION_UNIT/gstack-cso-core")"
240 cso_compile_posix_launcher "$CSO_EVALUATION_UNIT/gstack-cso-launcher" "$candidate_core_sha256"
241 chmod +x "$CSO_EVALUATION_UNIT/gstack-cso-launcher"
242 if [ "$(uname -s)" = Darwin ];then cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-launcher" 1;fi
243 printf '%s\n' "$candidate_core_sha256" > "$CSO_EVALUATION_UNIT/.gstack-cso-generation"
244 printf '{"schemaVersion":1,"evaluationOnly":true,"runtimeCatalogRevision":"%s","runtimeCatalogSha256":"%s","coreSha256":"%s"}\n' \
245 "$candidate_revision" "$(cso_sha256 "$source_root/lib/cso/runtime-catalog.json")" "$candidate_core_sha256" \
246 > "$CSO_EVALUATION_UNIT/.gstack-cso-evaluation"
247 for artifact in gstack-cso-core gstack-cso-launcher gstack-cso-watchdog cso-eval-producer;do
248 cso_valid_artifact "$CSO_EVALUATION_UNIT/$artifact" || { echo "Evaluation $artifact is not one executable regular file." >&2;return 1; }
249 done
250 cso_valid_generation "$CSO_EVALUATION_UNIT/.gstack-cso-generation" || { echo 'Evaluation generation manifest is invalid.' >&2;return 1; }
251 [ "$(cso_sha256 "$CSO_EVALUATION_UNIT/gstack-cso-core")" = "$candidate_core_sha256" ] || { echo 'Evaluation core changed after launcher binding.' >&2;return 1; }
252 "$CSO_EVALUATION_UNIT/gstack-cso-launcher" --version >/dev/null
253 chmod 0555 "$CSO_EVALUATION_UNIT"/gstack-cso-core "$CSO_EVALUATION_UNIT"/gstack-cso-launcher "$CSO_EVALUATION_UNIT"/gstack-cso-watchdog "$CSO_EVALUATION_UNIT"/cso-eval-producer
254 chmod 0444 "$CSO_EVALUATION_UNIT/.gstack-cso-generation" "$CSO_EVALUATION_UNIT/.gstack-cso-evaluation"
255 chmod 0755 "$CSO_EVALUATION_UNIT"
256 mv "$CSO_EVALUATION_UNIT" "$candidate_output"
257 CSO_EVALUATION_UNIT=""
258 printf '{"output":"%s","revision":"%s","evaluationOnly":true}\n' "$candidate_output" "$candidate_revision"
261cso_publish_locked() {
262 [ "${GSTACK_CSO_PUBLISH_LOCKED:-}" = 1 ] || { echo 'CSO publication requires the native publisher lock.' >&2;return 73; }
263 [ "$#" -eq 1 ] && [ -d "$1" ] && [ ! -L "$1" ] || { echo 'CSO publication stage is invalid.' >&2;return 69; }
264 CSO_STAGE="$(cd "$1" && pwd -P)"
265 case "$CSO_STAGE" in "$CSO_BUILD_ROOT"/bin/.gstack-cso-stage.*) ;; *) echo 'CSO publication stage escaped the trusted bin directory.' >&2;return 69;;esac
266 CSO_STAGE_CORE="$CSO_STAGE/gstack-cso-core$CSO_EXE"
267 CSO_STAGE_LAUNCHER="$CSO_STAGE/gstack-cso-launcher$CSO_EXE"
268 CSO_STAGE_WATCHDOG="$CSO_STAGE/gstack-cso-watchdog"
269 CSO_STAGE_GENERATION="$CSO_STAGE/.gstack-cso-generation"
270 cso_valid_artifact "$CSO_STAGE_CORE" || { echo 'Staged CSO core is not one executable regular file.' >&2;return 1; }
271 cso_valid_artifact "$CSO_STAGE_LAUNCHER" || { echo 'Staged CSO launcher is not one executable regular file.' >&2;return 1; }
272 if [ -z "$CSO_EXE" ];then cso_valid_artifact "$CSO_STAGE_WATCHDOG" || { echo 'Staged CSO watchdog is not one executable regular file.' >&2;return 1; };fi
273 cso_valid_generation "$CSO_STAGE_GENERATION"||{ echo 'Staged CSO generation manifest is invalid.' >&2;return 1; }
275 old_present=0
276 for artifact in "$CSO_FINAL_LAUNCHER" "$CSO_FINAL_CORE";do
277 if cso_present "$artifact";then cso_valid_artifact "$artifact" || { echo 'Existing CSO artifacts contain an unsafe file.' >&2;return 1; };old_present=$((old_present+1));fi
278 done
279 if [ -z "$CSO_EXE" ]&&cso_present "$CSO_FINAL_WATCHDOG";then cso_valid_artifact "$CSO_FINAL_WATCHDOG" || { echo 'Existing CSO artifacts contain an unsafe file.' >&2;return 1; };old_present=$((old_present+1));fi
280 if cso_present "$CSO_FINAL_GENERATION";then cso_valid_generation "$CSO_FINAL_GENERATION"||{ echo 'Existing CSO generation manifest is unsafe.' >&2;return 1; };old_present=$((old_present+1));fi
281 expected=3;[ -n "$CSO_EXE" ]||expected=4
282 if [ "$old_present" -eq "$expected" ];then CSO_OLD_COHERENT=1;fi
284 CSO_PUBLISHING=1
285 # Ignored signals remain ignored in mv children. SIGKILL is handled by the
286 # launcher-first withdrawal and launcher-last commit order.
287 trap '' HUP INT TERM
288 if cso_present "$CSO_FINAL_LAUNCHER";then
289 CSO_OLD_LAUNCHER_MOVED=1
290 cso_move "$CSO_FINAL_LAUNCHER" "$CSO_STAGE/previous/launcher"
291 cso_checkpoint withdraw-launcher
292 fi
293 if cso_present "$CSO_FINAL_CORE";then CSO_OLD_CORE_MOVED=1;cso_move "$CSO_FINAL_CORE" "$CSO_STAGE/previous/core";fi
294 if [ -z "$CSO_EXE" ]&&cso_present "$CSO_FINAL_WATCHDOG";then CSO_OLD_WATCHDOG_MOVED=1;cso_move "$CSO_FINAL_WATCHDOG" "$CSO_STAGE/previous/watchdog";fi
295 if cso_present "$CSO_FINAL_GENERATION";then CSO_OLD_GENERATION_MOVED=1;cso_move "$CSO_FINAL_GENERATION" "$CSO_STAGE/previous/generation";fi
297 CSO_NEW_CORE_INSTALLED=1
298 cso_move "$CSO_STAGE_CORE" "$CSO_FINAL_CORE"
299 cso_checkpoint publish-core
300 if [ -z "$CSO_EXE" ];then
301 CSO_NEW_WATCHDOG_INSTALLED=1
302 cso_move "$CSO_STAGE_WATCHDOG" "$CSO_FINAL_WATCHDOG"
303 cso_checkpoint publish-watchdog
304 fi
305 CSO_NEW_GENERATION_INSTALLED=1
306 cso_move "$CSO_STAGE_GENERATION" "$CSO_FINAL_GENERATION"
307 cso_checkpoint before-publish-launcher
308 CSO_NEW_LAUNCHER_INSTALLED=1
309 cso_move "$CSO_STAGE_LAUNCHER" "$CSO_FINAL_LAUNCHER"
310 cso_checkpoint publish-launcher
311 CSO_COMMITTED=1
314cso_start_result() {
315 CSO_RESULT_RECORDING=1
316 CSO_RESULT_STAGE=$1
317 CSO_REVISION="$(head -1 "$CSO_BUILD_ROOT/VERSION" 2>/dev/null | tr -cd '0-9A-Za-z.+-')"
318 revision_sha="$(git -C "$CSO_BUILD_ROOT" rev-parse --short=12 HEAD 2>/dev/null | tr -cd '0-9a-f')"
319 CSO_REVISION="${CSO_REVISION:-unknown}${revision_sha:+ ($revision_sha)}"
320 CSO_PREVIOUS_INSTALLED="$(cso_record_field installed)"
323if [ "${1:-}" = __publish_locked ];then
324 shift
325 cso_start_result publish
326 cso_publish_locked "$@"
327 exit 0
328fi
329if [ "${1:-}" = --evaluation-candidate ];then
330 cso_build_evaluation_candidate "$@"
331 exit 0
332fi
333[ "$#" -eq 0 ] || { echo 'Usage: build-cso.sh [--evaluation-candidate <cso-eval-catalog.json> --output <new-directory>]' >&2;exit 64; }
334cso_start_result build
335cso_write_result failed build interrupted
336# Distribution builds embed only the committed catalog, never an evaluation one.
337if [ -f lib/cso/runtime-catalog.json ] && grep -q '"revision": *"cso-eval-' lib/cso/runtime-catalog.json;then
338 echo 'lib/cso/runtime-catalog.json is an evaluation-only catalog; distribution builds refuse it. Restore the committed catalog.' >&2
339 exit 65
340fi
342# This entrypoint can be run directly. Once it may publish a CSO generation,
343# the previous whole-build completion proof no longer describes all outputs.
344rm -f "$CSO_BUILD_ROOT/browse/dist/.build-complete"
346if ! command -v "$CSO_CC" >/dev/null 2>&1 && [ -z "$CSO_EXE" ]; then
347 echo 'CSO build requires a C compiler (cc/clang/gcc) for the trusted launcher and watchdog.' >&2
348 exit 1
349fi
350if [ "$(uname -s)" = Darwin ] && ! command -v codesign >/dev/null 2>&1; then
351 echo 'CSO build requires macOS codesign so staged artifacts can be verified before publication.' >&2
352 exit 1
353fi
355CSO_STAGE="$(mktemp -d "$CSO_BUILD_ROOT/bin/.gstack-cso-stage.XXXXXX")"
356CSO_STAGE_CORE="$CSO_STAGE/gstack-cso-core$CSO_EXE"
357CSO_STAGE_LAUNCHER="$CSO_STAGE/gstack-cso-launcher$CSO_EXE"
358CSO_STAGE_WATCHDOG="$CSO_STAGE/gstack-cso-watchdog"
359CSO_STAGE_GENERATION="$CSO_STAGE/.gstack-cso-generation"
360CSO_STAGE_LOCKER="$CSO_STAGE/gstack-cso-publish-lock$CSO_EXE"
361mkdir -m 700 "$CSO_STAGE/previous"
362if [ -n "$CSO_EXE" ];then : > "$CSO_STAGE/.gstack-cso-generation.lock";fi
364# These are compile-time switches. Bun otherwise discovers configuration in
365# the audited working directory before the helper can apply its own policy.
366cso_compile_bun lib/cso/cli.ts "$CSO_STAGE_CORE"
367cso_checkpoint core
369# POSIX process groups are required by the detached watchdog. Windows keeps
370# comprehensive execution unavailable instead of substituting a weaker timer.
371if [ -z "$CSO_EXE" ]; then
372 cso_compile_watchdog "$CSO_STAGE_WATCHDOG"
373 cso_checkpoint watchdog
374fi
376if [ "$(uname -s)" = Darwin ]; then
377 cso_sign_macos_artifact "$CSO_STAGE_CORE" 0
378 cso_sign_macos_artifact "$CSO_STAGE_WATCHDOG" 0
379fi
380CSO_CORE_SHA256="$(cso_sha256 "$CSO_STAGE_CORE")"
381printf '%s\n' "$CSO_CORE_SHA256" > "$CSO_STAGE_GENERATION"
383if [ -n "$CSO_EXE" ];then
384 # PowerShell 7 first: Windows PowerShell 5.1 can crash outright (#3071).
385 CSO_POWERSHELL="$(command -v pwsh 2>/dev/null || command -v powershell.exe 2>/dev/null || true)"
386 if [ -z "$CSO_POWERSHELL" ]||! command -v cygpath >/dev/null 2>&1;then
387 echo 'CSO Windows build requires Git Bash and PowerShell (pwsh or Windows PowerShell) with MSVC Build Tools.' >&2;exit 1
388 fi
389 CSO_WINDOWS_GIT="$(type -P git 2>/dev/null || true)"
390 [ -n "$CSO_WINDOWS_GIT" ] && [ -f "$CSO_WINDOWS_GIT" ] || { echo 'CSO Windows build requires the Git for Windows git.exe selected by Git Bash.' >&2;exit 1; }
391 "$CSO_POWERSHELL" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
392 -File "$(cygpath -w "$CSO_BUILD_ROOT/scripts/build-cso-windows.ps1")" \
393 -RepoRoot "$(cygpath -w "$CSO_BUILD_ROOT")" \
394 -OutputPath "$(cygpath -w "$CSO_STAGE_LAUNCHER")" \
395 -LockOutputPath "$(cygpath -w "$CSO_STAGE_LOCKER")" \
396 -CoreSha256 "$CSO_CORE_SHA256" \
397 -GitExePath "$(cygpath -aw "$CSO_WINDOWS_GIT")"
398else
399 cso_compile_posix_launcher "$CSO_STAGE_LAUNCHER" "$CSO_CORE_SHA256"
400 "$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra lib/cso/publish-lock.c -o "$CSO_STAGE_LOCKER"
401fi
402chmod +x "$CSO_STAGE_LAUNCHER" "$CSO_STAGE_LOCKER"
403cso_checkpoint launcher
404cso_checkpoint publish-lock
406if [ "$(uname -s)" = Darwin ];then cso_sign_macos_artifact "$CSO_STAGE_LAUNCHER" 1;fi
408cso_valid_artifact "$CSO_STAGE_CORE" || { echo 'Staged CSO core is not one executable regular file.' >&2; exit 1; }
409cso_valid_artifact "$CSO_STAGE_LAUNCHER" || { echo 'Staged CSO launcher is not one executable regular file.' >&2; exit 1; }
410cso_valid_artifact "$CSO_STAGE_LOCKER" || { echo 'Staged CSO publisher lock is not one executable regular file.' >&2; exit 1; }
411if [ -z "$CSO_EXE" ]; then cso_valid_artifact "$CSO_STAGE_WATCHDOG" || { echo 'Staged CSO watchdog is not one executable regular file.' >&2; exit 1; }; fi
412cso_valid_generation "$CSO_STAGE_GENERATION"||{ echo 'Staged CSO generation manifest is invalid.' >&2;exit 1; }
413[ "$(cso_sha256 "$CSO_STAGE_CORE")" = "$CSO_CORE_SHA256" ] || { echo 'Staged CSO core changed after launcher binding.' >&2;exit 1; }
414"$CSO_STAGE_LAUNCHER" --version >/dev/null
415cso_checkpoint validated
417if [ -n "$CSO_EXE" ];then
418 [ -f /usr/bin/bash.exe ] || { echo 'CSO publication requires the Git Bash executable.' >&2;exit 1; }
419 CSO_PUBLISH_SHELL="$(cygpath -aw /usr/bin/bash.exe)"
420else
421 case "${BASH:-}" in /*) CSO_PUBLISH_SHELL=$BASH;; *) echo 'CSO publication requires an absolute Bash executable.' >&2;exit 1;;esac
422fi
423cso_valid_artifact "$CSO_STAGE_LOCKER" || { echo 'Staged CSO publisher lock is not one executable regular file.' >&2; exit 1; }
424CSO_RESULT_STAGE=publish
425cso_write_result failed publish interrupted
426cso_checkpoint before-exec
427# From here the locked child owns the stage: its own EXIT trap restores and
428# cleans it. Git Bash's exec of a native program may not replace this shell,
429# so this trap must not be armed under the running child (#3071). If the exec
430# itself fails, clean up here.
431set +e
432shopt -s execfail
433trap - EXIT
434exec "$CSO_STAGE_LOCKER" "$CSO_BUILD_ROOT/bin" "$CSO_PUBLISH_SHELL" "$CSO_BUILD_ROOT/scripts/build-cso.sh" __publish_locked "$CSO_STAGE"
435cso_exec_status=$?
436echo "CSO publication could not start $CSO_STAGE_LOCKER (exit $cso_exec_status)." >&2
437cso_cleanup "$cso_exec_status"