Repository navigation
ci(fat): verify fat builds across compilers, macOS, toggles, and all examples - #428
Merged
Merged
Conversation
…examples Extend the fat archive workflow with the coverage the single Ubuntu/gcc job missed: - fat: matrix over gcc and clang for the bundle build and link smoke. - fat-macos: exercises the Darwin libtool merge path (the GNU ar MRI script never runs there) plus member/symbol checks and the embedded-style link smoke. - fat-toggle: flipping CWIST_FAT off/on and CWIST_APP_FAT=1 must re-archive libcwist.a without recompiling a single object, and the fat-dist bundle must contain every header the public API pulls in. - fat-examples: builds all examples with CWIST_APP_FAT=1 and asserts no vendored archive appears on any example link line.
gg582
added a commit
that referenced
this pull request
Oct 25, 2026
…#445) * ci(fat): verify fat builds across compilers, macOS, toggles, and all examples (#428) Extend the fat archive workflow with the coverage the single Ubuntu/gcc job missed: - fat: matrix over gcc and clang for the bundle build and link smoke. - fat-macos: exercises the Darwin libtool merge path (the GNU ar MRI script never runs there) plus member/symbol checks and the embedded-style link smoke. - fat-toggle: flipping CWIST_FAT off/on and CWIST_APP_FAT=1 must re-archive libcwist.a without recompiling a single object, and the fat-dist bundle must contain every header the public API pulls in. - fat-examples: builds all examples with CWIST_APP_FAT=1 and asserts no vendored archive appears on any example link line. (cherry picked from commit 4b02b6a) * feat(build): compile-time feature trimming (CWIST_HTTP3/WS/DB/NATS/JWT/TINY) (#435) * feat(build): add compile-time feature-trimming knobs New make knobs drop whole subsystems from libcwist.a at compile time: CWIST_HTTP3=0 http3.c/http3_client.c and liblsquic leave the build; CWIST_WEBTRANSPORT is forced to none. CWIST_WS=0 websocket*.c, ws_utils.c and graphql_ws.c (rides on websocket_async) leave the build. CWIST_DB=0 sqlite3, src/core/db, src/core/orm, db_sync and db_crypt leave the build. CWIST_NATS=0 cwist_nats.c and libnats_static leave the build. CWIST_JWT=0 jwt.c leaves the build. CWIST_TINY=1 meta-knob defaulting all of the above plus CWIST_WEBRTC and CWIST_WEBTRANSPORT=none to off; individual knobs can still be re-enabled explicitly. Semantics follow the existing CWIST_WEBRTC toggle and the WASM stub idiom: public headers are untouched and the app-level entry points (cwist_app_use_http3/https3/webtransport, cwist_app_use_db/nuke_db/db_pool, the cwist_app_ws* family, cwist_mw_jwt_auth, cwist_job_queue_create_nats) keep existing as stubs that report the feature as unavailable (err_i16 = -1 / NULL), so hello-style apps link unchanged. Module-level APIs of a dropped feature (cwist_db_*, cwist_orm_*, cwist_jwt_*, cwist_websocket_*) are absent at link time by design. Guarded call sites: app.c (HTTP/3 context lifecycle, multiport H3 fan-out, WS upgrade path, DB teardown), big_dumb_reply.c (SQLite disk-spill path; the in-RAM cache stays), middleware.c (JWT auth handler), durable_queue.c (NATS JetStream backend; the Redis backend stays). LIBS keeps the vendored archives in their historical position before the system libraries via inline $(if) filters: appending them at the end put liblsquic after -lm and broke --as-needed DSO resolution (lsqpack's roundf). Tests for trimmed features use the CWIST_WEBRTC else-stub echo pattern; test_json_heal, test_builtin_mw_chain and test_tls_hot_reload join the brief's list because they call module-level symbols directly. Feature-dependent examples are filtered out of EXAMPLE_BINS/MICRO_BINS/ STANDALONE_EXAMPLE_DIRS when their knob is off; default-flag example builds are unchanged. fat-dist/install now pull the sqlite3 amalgamation download explicitly so header staging works on fresh trimmed checkouts. Verified: default, CWIST_TINY=1, CWIST_DB=0 and CWIST_HTTP3=0 builds; fat-dist member assertions both ways; embedded-style hello smoke on the trimmed bundles; make test green under default and CWIST_TINY=1; check_api_baseline/check_test_wiring/check_env_knobs all green. * ci(fat): add fat-trim matrix job for CWIST_TINY=1 and CWIST_DB=0 Builds the trimmed fat archives, asserts the dropped members are gone (no lsqpack/sqlite3/nats objects as applicable), asserts the kept core stays (cJSON.o, SSL_new), runs the same embedded-style hello link smoke as the full fat job, and records archive sizes. (cherry picked from commit e78d6a4) * feat(build): amalgamated single-file distribution (cwist.c drop-in) (#436) * refactor(src): unique-ify statics and guard macros for unity builds Preparation for the amalgamated single-file distribution (issue #429): pasting every translation unit into one cwist.c makes file-local identifiers collide across former TUs and lets file-local macros leak into later bodies. Static renames (one side of each collision, module-prefixed): - assets.c: add_header -> assets_add_header (collided with html_response.c's add_header) - app.c: cwist_mem_now -> cwist_app_mem_now (collided with alloc.c's cwist_mem_now) - uring_sqpoll.c: sys_io_uring_setup -> sqpoll_io_uring_setup (collided with reactor.c's sys_io_uring_setup) - session.c: base64_encode -> session_base64_encode, hmac_sha256 -> session_hmac_sha256 (collided with ws_utils.c's public base64_encode and jwt.c's hmac_sha256) - sse.c: send_all -> sse_send_all (collided with orm_socket.c's send_all) - db_sync.c: write_all -> db_sync_write_all (collided with log_async.c's write_all) Macro hygiene: - siphash.c: guard ROTL and #undef it at end of file; sqlite3.c pastes its own unguarded ROTL later in the unity TU. - app.c, nuke_db.c: guard _DEFAULT_SOURCE; in the unity TU features.h has already defined it via an earlier _GNU_SOURCE include. All identifiers are static (file-local); the per-TU build, tests, and the public API surface are unchanged. * feat(build): amalgamated single-file distribution (make amalgamation) Implements issue #429: 'make amalgamation' stages a drop-in bundle in dist/amalgamation/: - cwist.c: every enabled translation unit (the knob-expanded SRCS, in Makefile order) pasted into one file by scripts/gen_amalgamation.py. Compile-time defines (-D from CFLAGS) are embedded as #define lines, each source body is preceded by a #line marker, and quoted includes are rewritten to the staged header layout, so the whole file compiles with just: gcc -O2 -std=gnu17 -Idist/amalgamation/include -c cwist.c - include/: the public cwist tree plus the vendored headers the public API references (cjson, ttak, openssl, sqlite3.h; same staging as fat-dist), a generated cwist.h umbrella header, private src/ headers under cwist-priv/, and feature-gated vendored headers (lsquic, cnats, usrsctp) under cwist-vendor/ so generically named headers never sit at the include root. The trim knobs apply exactly as for libcwist.a: 'make CWIST_TINY=1 amalgamation' emits the 73-source tiny build, the default emits 96 sources including the sqlite3 amalgamation. Output is deterministic (same inputs, byte-identical cwist.c). Vendored code still links from its static archives, e.g. via 'make fat-dist' alongside. The default build path is untouched; the target is opt-in and works from a clean checkout (submodule header anchors initialize submodules like the existing lsquic.h rule). * ci(amalgamation): build and smoke-test the drop-in bundle New workflow following the fat.yml pattern: for the default and CWIST_TINY=1 configurations, generate the amalgamation plus the fat archive, copy the bundle out of the repo tree, compile cwist.c with a single -I, archive it, link example/micro/01-hello against it and the fat archive (vendored code), then curl the server for 'Hello, CWIST!'. * style(session): clang-format after the unity-build renames (cherry picked from commit 5face63) * feat(tls): pluggable TLS backend with an mbedTLS option for embedded targets (#437) * feat(tls): pluggable TLS backend with an mbedTLS option for embedded targets CWIST_TLS=boringssl (default, unchanged behaviour) | CWIST_TLS=mbedtls selects the TLS/crypto backend. With mbedtls (vendored as lib/mbedtls, pinned to the v3.6.7 LTS tag, built with the same cmake stamp-file pattern as BoringSSL) the produced libcwist.a - thin or fat - contains zero BoringSSL objects and no SSL_* symbols. The seam: call sites keep their OpenSSL-shaped code. Under CWIST_TLS=mbedtls the include path swaps lib/boringssl/include for src/security/tls/shim/, whose openssl/*.h headers alias every SSL_*/HMAC_*/ EVP_* name CWIST uses to cwist_mtls_* functions genuinely implemented on mbedTLS in src/security/tls/backend_mbedtls.c (server/client TLS 1.2+1.3, cert chain + key load, ALPN, SNI, session cache, hot reload, error mapping, fork-aware CTR-DRBG). Public headers are untouched (SSL/SSL_CTX become opaque typedefs), so the API baseline stays green. Forced off under mbedtls (Makefile-level, with warnings): CWIST_HTTP3 (lsquic needs BoringSSL's QUIC API), CWIST_WEBTRANSPORT, CWIST_WEBRTC (DTLS). Degraded at runtime, documented in docs/performance/tls-backends.md: JWT EdDSA (no Ed25519 in mbedTLS 3.6; HS256/ES256 work), the session resumption counter (no public per-connection flag), TLS 1.3 NewSessionTicket issuance (mbedTLS writes it after the wrap-up, racing clients that close immediately; TLS 1.2 cache resumption keeps working), AIA intermediate autoload (no-op stub), ECH, and the hybrid PQC group list (warns, enforces TLS 1.3 classical groups). jwt.c gets real CWIST_TLS_MBEDTLS branches (HS256 via mbedtls_md, ES256 via mbedtls_ecdsa with JOSE raw R||S); session HMAC, seq_auth HMAC and db_crypt AES-256-CBC (PKCS#7, format-compatible) ride the shim unchanged. Also fixes a latent Makefile gap: the .build-flags rebuild guard was attached to $(OBJS) before the feature-gated source appends, so flag flips never rebuilt feature objects (jwt.o, http3.o, ...); the attachment now sits after the appends. CI: new fat-mbedtls job in fat.yml builds the bundle, asserts no BoringSSL members/symbols, links example/micro/18-https-hello against the bundle and curl -k smokes it, then runs the TLS-adjacent test subset. Default-flag jobs are untouched; make test stays green on the default build. Closes #430 * refactor(tls): route the mbedTLS backend through cwist_malloc/cwist_free The raw-allocator audit gate (cwist audit --gate) fails on bare calloc/free in src/. cwist_malloc/cwist_free is the right wrapper here: SSL_CTX/SSL objects deliberately cross threads (handshake shepherd to pool worker, reload thread), so they must not sit on a thread's full-GC sweep list - same reason https.c uses cwist_malloc for the pool task ring. Also clang-format the new seam files. (cherry picked from commit 10a8b39) * ci(build): first-class cross-compilation for embedded targets (#438) * ci(build): first-class cross-compilation for embedded targets (#431) Honor CROSS_COMPILE=<triplet>- in the Makefile (Closes #431): Makefile: - Added CROSS_COMPILE support block near the top that sets CC, CXX, AR, RANLIB, and STRIP from the prefix when CROSS_COMPILE is set and the individual tools are not already in the environment. - Derives a CMake system name from the triplet and populates EXTRA_CMAKE_FLAGS with CMAKE_SYSTEM_NAME, CMAKE_C_COMPILER, CMAKE_CXX_COMPILER, CMAKE_AR, and CMAKE_RANLIB so that the vendored cmake dependencies (uriparser, BoringSSL, lsquic) pick up the same cross toolchain without separate cmake invocations. - EXTRA_CMAKE_FLAGS appended to the cmake -S ... configure calls for uriparser, BoringSSL, and lsquic. .github/workflows/cross-compile.yml: - Matrix: aarch64-linux-gnu and arm-linux-gnueabihf. - Runs on every push/PR to main/master/dev (same cadence as other build checks) and on workflow_dispatch. - Builds fat-dist with the corresponding CROSS_COMPILE prefix. - Smoke-checks the fat archive with qemu-user to confirm the objects are of the expected target architecture. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(cross): build fat-dist with the target toolchain and serve it under qemu CC ?= / AR ?= never fire because make predefines both, so the cross job compiled with the host cc and died linking uriparser with a host ar. Take CC/CXX/AR from CROSS_COMPILE unless the caller set them, hand the same toolchain (absolute ar/ranlib, CMAKE_SYSTEM_PROCESSOR) to every vendored CMake build including cnats, usrsctp and mbedTLS, route pkg-config through PKG_CONFIG, and build libttak without -march=native. The workflow installs target dev packages from ports.ubuntu.com, links the hello example against the bundle with the cross gcc and curls it under qemu-user instead of compiling a missing tests/smoke_load.c behind '|| true'. armhf is dropped until libttak's accel_cpu.c stops requiring __int128. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lee Yunjin <gzblues61@daum.net> (cherry picked from commit 6c58d9d) * feat(api): runtime OpenAPI 3.1 generation from the route table (#439) * feat(api): runtime OpenAPI 3.1 generation from the route table (#408) Extends include/cwist/openapi.h with a runtime document generator and adds the implementation in src/sys/app/openapi.c. (Closes #408) include/cwist/openapi.h: - Added cwist_openapi_meta_t (title, version, description, server_url) for per-app document metadata. - Added cwist_app_enable_openapi(app, json_path, meta): registers a GET handler at json_path (default "/openapi.json") that serves an OpenAPI 3.1 JSON document built from annotated routes. Must be called before any cwist_openapi_annotate() call and before listen. - Added cwist_openapi_annotate(app, method, path, summary, tags): stores per-route metadata (method, path, summary, comma-separated tags) for inclusion in the spec. Calling it after cwist_app_enable_openapi() invalidates the cached document so the next request rebuilds it with the new route. - Existing CWIST_OPENAPI_* source-level macros are unchanged. src/sys/app/openapi.c: - Fixed-size route table (CWIST_OPENAPI_MAX_ROUTES = 256, tunable). - build_json() emits a minimal but valid OpenAPI 3.1 document covering the info object, a single server entry, and each annotated path with its operation object (summary, tags, a 200 response stub). - The document is built on the first GET request and cached; any subsequent cwist_openapi_annotate() call invalidates the cache. - All state is protected by a per-process mutex; building and serving are safe under concurrent requests. docs/api/v4.0-api-baseline.txt: regenerated via make api-baseline to add cwist_app_enable_openapi, cwist_openapi_annotate, and cwist_openapi_meta_t. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(openapi): build openapi.c and cover the runtime endpoint src/sys/app/openapi.c was never added to SRCS, so it hid two compile errors (res->status, cwist_http_set_header). Use status_code and cwist_http_header_add, wire the file into the library, and add test_openapi, which fetches the served spec through the test client and checks it parses with the metadata and the annotated operation. * refactor(openapi): generate the document from the live route table The endpoint only listed operations passed to cwist_openapi_annotate(), so a plain cwist_app_get() route never appeared, contrary to the header and to #408 (routes without annotations still show up). It also kept one process-wide state for every app, stored the caller's meta pointers, emitted a separate key per operation (duplicate "/users" keys for GET and POST) and hand-assembled JSON in a fixed 64 KiB buffer. Walk the route table on each request through a private iterator in app.c (routes_internal.h), build the document with cJSON, map :id segments to {id} path parameters, and keep metadata and annotations in per-app state owned by the spec route, so it is freed with the app. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lee Yunjin <gzblues61@daum.net> (cherry picked from commit 24908e0) * ci(mem): zero-allocation gate for the hot path (#440) * ci(mem): zero-allocation gate for the hot path (#432) Adds the CI tooling to verify that the CWIST request path performs no heap allocations after cwist_app_listen() completes initialization. (Closes #432) tests/zero_alloc_counter.c: An LD_PRELOAD shim that interposes malloc/calloc/realloc via RTLD_NEXT. Three atomic operations control it from the gate server process: - cwist_zero_alloc_arm() starts counting - cwist_zero_alloc_disarm() stops counting - cwist_zero_alloc_count() returns the accumulated count The counter is thread-safe (C11 atomics); the interposition is per-process and intentionally does not intercept vendored libraries (BoringSSL, SQLite) because CWIST's own allocator hooks gate that path. scripts/ci/zero_alloc_gate.sh: 1. Builds the shim if stale. 2. Starts rss_gate_server (HTTP) with CWIST_ZERO_ALLOC_GATE=1 and LD_PRELOAD pointing at the shim. 3. Sends ZERO_ALLOC_WARMUP_REQS (default 200) warm-up requests to let arenas, caches and ring buffers reach steady state. 4. Arms the counter via /zero_alloc_arm, sends ZERO_ALLOC_GATE_REQS (default 1000) gate requests, disarms via /zero_alloc_count. 5. PASS if the reported count == 0; FAIL otherwise. Writes a summary block to GITHUB_STEP_SUMMARY when running under GitHub Actions. All limits are environment-variable overrides. .github/workflows/zero-alloc.yml: - Runs on push/PR to main/master/dev and nightly at 03:00 UTC. - workflow_dispatch exposes warmup_reqs and gate_reqs overrides for targeted manual runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci(mem): measure hot-path allocations against a budget The gate could not run: it spoke plain HTTP to rss_gate_server, which only serves HTTPS, and called /zero_alloc_arm and /zero_alloc_count routes that no server registered. The shim resolved the allocators with dlsym(RTLD_NEXT), which itself calls calloc and recurses, and the counter would have been per worker while the requests were spread over four. - tests/zero_alloc_server.c: plain HTTP, one worker, arm/count routes bound to the preloaded shim through dlsym(RTLD_DEFAULT) - the shim calls glibc's __libc_* allocators and also counts the aligned ones - the gate sends every request over one keep-alive connection and subtracts the cost of the arm/count requests themselves Measured: exactly 2 allocations per request (the response body sstring), so the hot path is not allocation-free yet. The gate fails anything above ZERO_ALLOC_BUDGET=2 and the deterministic memory mode of #432 lowers it to 0. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lee Yunjin <gzblues61@daum.net> (cherry picked from commit 0ba2ed0) * feat(middleware): OpenTelemetry tracing export (#442) * feat(middleware): OpenTelemetry tracing export (#402) Adds cwist_mw_otel_trace() and cwist_mw_otel_get_trace_id() to the middleware layer. (Closes #402) include/cwist/sys/app/middleware.h: - cwist_otel_proto_t: CWIST_OTEL_PROTO_HTTP (default) and CWIST_OTEL_PROTO_GRPC. - cwist_otel_cfg_t: endpoint URL, service_name, proto, queue_cap, batch_size; all optional, NULL falls back to built-in defaults (http://localhost:4318/v1/traces, "cwist", 512-span queue, 64 per batch). - cwist_mw_otel_trace(cfg): factory that initializes a process-global OTel state on first call (subsequent calls return the same middleware func) and returns a cwist_middleware_func for use with cwist_app_use(). - cwist_mw_otel_get_trace_id(req): reads the W3C trace-id propagated into the request by the middleware. src/sys/app/otel.c: - Middleware function: reads the incoming "traceparent" header, extracts or generates a 128-bit trace-id and 64-bit span-id, stores the trace-id on the request as "X-Cwist-Trace-Id", calls next(), then records method/path/status/duration and enqueues the span. - Bounded ring-buffer queue (default 512); when full the oldest span is dropped silently (fail-open: collector downtime never fails requests). - Background exporter thread: batches spans and calls export_spans(). export_spans() is a JSON-over-HTTP scaffold for v4.1; the TODO comment marks where a real OTLP/HTTP POST (via the cwist HTTP client) goes. docs/api/v4.0-api-baseline.txt: regenerated to add cwist_mw_otel_trace, cwist_mw_otel_get_trace_id, cwist_otel_cfg_t, cwist_otel_proto_t, CWIST_OTEL_PROTO_HTTP, CWIST_OTEL_PROTO_GRPC. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(otel): build otel.c and cover trace-id propagation src/sys/app/otel.c was never added to SRCS, which hid three compile errors (res->status, cwist_sstring_cstr, the int method table). Fix them, wire the file into the library, and add test_otel: a traceparent trace id reaches the handler, and a request without one gets a fresh 128-bit id. * refactor(otel): export real OTLP/HTTP spans from every worker export_spans() built a JSON string and threw it away (a TODO in place of the POST), so no span ever reached a collector. The exporter thread was also created before cwist_app_listen() forks the workers, so the children never had one; ids came from rand(); spans had no timestamps; traceparent was accepted unvalidated; and the trace id lived in an X-Cwist-Trace-Id request header that a client could set itself. - POST batches as OTLP/HTTP JSON (cJSON, cwist_http_client, 2 s timeout), with start/end times, SERVER kind, semconv attributes and error status for 5xx; failures drop the batch (fail-open) - start the exporter on a process's first span and reset it in a pthread_atfork child hook, as log_async.c does - RAND_bytes ids; W3C validation (hex, non-zero ids, version ff) - keep the trace id in a thread-local context like the JWT claims - drop cwist_otel_proto_t: gRPC was never implemented - test_otel receives the export on a loopback collector socket --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Lee Yunjin <gzblues61@daum.net> (cherry picked from commit 01f59c4) * fix(otel): module-prefix otel.c statics for the amalgamation (#444) * fix(otel): module-prefix otel.c statics for the amalgamation The amalgamation pastes every translation unit into one cwist.c, and otel.c's g_lock, g_work and atfork_* redefined log_async.c's, so both Amalgamation jobs fail on dev since #442. Prefix every file-scope static in otel.c with otel_, as #436 did for the earlier collisions. * style(otel): prefix parse_traceparent and wrap to the 100-column limit The rename left parse_traceparent unprefixed and pushed six lines past .clang-format's ColumnLimit; clang-format otel.c and test_otel.c. (cherry picked from commit 79c807a) * ci(release): attach CI-built targets to every published release (#443) * build: CWIST_PORTABLE=1 builds libttak without -march=native libttak tunes for the build host by default, so an archive built on one machine can use instructions another CPU of the same architecture lacks. CWIST_PORTABLE=1 hands libttak the same portable flag set a cross build already used; CROSS_COMPILE now implies it. CWIST's own objects never used host tuning. * ci(release): attach CI-built targets to every published release release-assets.yml runs when a release is published (or by hand for an existing tag) and builds from the tag, with CWIST_PORTABLE=1: - fat-dist bundles: linux-x86_64, linux-x86_64-mbedtls, linux-aarch64 (cross, smoke-run under qemu-user) and macos-arm64 - the amalgamation, default and CWIST_TINY=1 - the cwist-wasm npm package Each bundle is linked and served by scripts/ci/bundle_smoke.sh before upload, the upload job runs only after every build passed, and it adds a SHA256SUMS file. The maintainer-uploaded source tarball that the Homebrew formula pins is never touched. Linux assets build on ubuntu-22.04 so the archives do not reference glibc 2.38+ symbols. docs/deployment/release-assets.md (and the ko mirror) lists the assets and how to link them. * ci(release): run the amalgamation smoke through bundle_smoke.sh The amalgamation job carried its own copy of the link, run and curl loop. bundle_smoke.sh takes APP_OBJS (objects linked ahead of the archive), so both jobs share one smoke. (cherry picked from commit 1378ac5) --------- Co-authored-by: Bhumika Gurav <guravbhumika808@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
.github/workflows/fat.yml(added in #427) with the coverage the single Ubuntu/gcc job missed:libtool -staticmerge path, which the GNU ar MRI script job never touches; includes member/symbol checks and the embedded-style link smoke (bundle + app source only).thin → fat → thin → CWIST_APP_FAT=1flips must re-archivelibcwist.awithout recompiling any object (guards the.build-fatstamp logic), and thefat-distbundle must stage every header the public API pulls in (cjson, openssl, ttak, sqlite3).CWIST_APP_FAT=1and asserts no vendored archive path appears on any example link line (guards theAPP_LIBSfiltering).All new assertions were verified locally on Linux; the macOS job mirrors the dependency setup precedent from
zig.yml.Test plan