diff --git a/.github/workflows/fat.yml b/.github/workflows/fat.yml
new file mode 100644
index 000000000..0d5a8b4ad
--- /dev/null
+++ b/.github/workflows/fat.yml
@@ -0,0 +1,83 @@
+name: "Fat archive"
+
+# CWIST_FAT=1 folds the vendored static archives into libcwist.a and
+# `make fat-dist` stages it with the headers the public API pulls in, so an
+# embedded target needs only the bundle plus the app source at link time;
+# CWIST_APP_FAT=1 links the example apps against that fat archive alone.
+# This job builds the bundle, proves a server app links and serves with
+# nothing but the bundle copied next to its source, and builds an example
+# with CWIST_APP_FAT=1.
+
+on:
+ push:
+ branches: [main, master, dev]
+ pull_request:
+ branches: [main, master, dev]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ fat:
+ name: Fat archive build + embedded-style link smoke
+ if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y cmake libcurl4-openssl-dev libnghttp2-dev libbrotli-dev libzstd-dev
+
+ - name: Build the fat archive and embedded bundle
+ run: make fat-dist -j"$(nproc)"
+
+ - name: Verify vendored archives are folded in
+ run: |
+ # Members of every vendored archive must be present, not just the
+ # CWIST objects: the archive alone has to satisfy an app link.
+ ar t libcwist.a | grep -q '^cJSON\.o$'
+ ar t libcwist.a | grep -q '^bio_ssl\.cc\.o$'
+ ar t libcwist.a | grep -q '^lsqpack\.c\.o$'
+ ar t libcwist.a | grep -q '^UriCommon\.c\.o$'
+ nm --defined-only libcwist.a | grep -qw 'T SSL_new'
+ nm --defined-only libcwist.a | grep -qw 'T lsquic_engine_new'
+ nm --defined-only libcwist.a | grep -qw 'T natsConnection_Connect'
+ nm --defined-only libcwist.a | grep -qw 'T sqlite3_open'
+
+ - name: Embedded-style link smoke (bundle + app source only)
+ run: |
+ # Mirror the embedded workflow: copy just the fat-dist bundle and
+ # the app source onto the target, link against system libs only.
+ work=$(mktemp -d)
+ cp -r dist/cwist-fat "$work/bundle"
+ cp example/micro/01-hello/main.c "$work/"
+ cd "$work"
+ "${CC:-gcc}" -O2 -std=gnu17 -Ibundle/include -o hello main.c \
+ bundle/lib/libcwist.a \
+ $(pkg-config --libs libcurl libnghttp2 libbrotlienc libbrotlicommon libbrotlidec libzstd) \
+ -pthread -ldl -lm -lstdc++ -lz
+ ./hello &
+ pid=$!
+ sleep 2
+ test "$(curl -s -m 3 http://127.0.0.1:8080/)" = "Hello, CWIST!"
+ kill "$pid"
+
+ - name: CWIST_APP_FAT example build
+ run: |
+ # The app link must reference libcwist.a and no vendored archive.
+ out=$(make CWIST_APP_FAT=1 example/micro/01-hello/hello 2>&1 | tail -1)
+ echo "$out"
+ echo "$out" | grep -q 'libcwist\.a'
+ ! echo "$out" | grep -q 'lib/[a-z0-9_/-]*/[^ ]*\.a'
+ ./example/micro/01-hello/hello &
+ pid=$!
+ sleep 2
+ test "$(curl -s -m 3 http://127.0.0.1:8080/)" = "Hello, CWIST!"
+ kill "$pid"
diff --git a/Makefile b/Makefile
index 515eaf869..cc8a245d4 100644
--- a/Makefile
+++ b/Makefile
@@ -335,6 +335,7 @@ SRCS = src/core/sstring/sstring.c \
EMCC ?= emcc
EMAR ?= emar
NODE ?= node
+RANLIB ?= ranlib
WASM_BUILD_DIR = .wasm-build
WASM_SRCS = src/core/sstring/sstring.c \
src/core/seq/seq.c \
@@ -701,8 +702,12 @@ LIB_NAME = libcwist.a
# flags and every object depends on it, so a changed flag forces a rebuild.
.build-flags: FORCE
@printf '%s' '$(CFLAGS)' | cmp -s - $@ || printf '%s' '$(CFLAGS)' > $@
+# Records the fat-archive toggle so flipping CWIST_FAT re-archives libcwist.a
+# without forcing a recompile of unchanged objects.
+.build-fat: FORCE
+ @printf '%s' 'fat=$(CWIST_FAT)' | cmp -s - $@ || printf '%s' 'fat=$(CWIST_FAT)' > $@
FORCE:
-.PHONY: FORCE
+.PHONY: FORCE fat-dist
$(OBJS): .build-flags
LIBTTAK_DIR = lib/libttak
LIBTTAK_LIB = $(LIBTTAK_DIR)/lib/libttak.a
@@ -761,6 +766,19 @@ ifeq ($(CWIST_WEBRTC),1)
LIBS += $(USRSCTP_LIB)
endif
+# --- Fat (embedded-friendly) build options -----------------------------------
+# CWIST_FAT=1 folds every vendored static archive into libcwist.a, so copying
+# the single archive plus the CWIST headers onto an embedded target is enough
+# to link a server app; only system libraries stay external.
+# CWIST_APP_FAT=1 builds the example apps against the fat archive alone (the
+# vendored archives drop off their link line); it implies CWIST_FAT=1.
+ifeq ($(CWIST_APP_FAT),1)
+CWIST_FAT = 1
+APP_LIBS = $(filter-out $(EXTERNAL_LIBS),$(LIBS))
+else
+APP_LIBS = $(LIBS)
+endif
+
# --- Build Targets ---
all: $(LIBTTAK_LIB) $(CJSON_LIB) $(URIPARSER_LIB) $(SQLITE_DIR)/sqlite3.c $(LSQUIC_LIB) $(LIB_NAME)
@@ -812,10 +830,39 @@ lib/lsquic/include/lsquic.h:
git submodule update --init --recursive $(LSQUIC_DIR); \
fi
-$(LIB_NAME): $(EXTERNAL_LIBS) $(OBJS)
- @echo "Creating thin static library..."
+$(LIB_NAME): .build-fat $(EXTERNAL_LIBS) $(OBJS)
@rm -f $@
- ar rcs $@ $(OBJS)
+ @if [ "$(CWIST_FAT)" = "1" ]; then \
+ echo "Creating fat static library (CWIST objects + vendored archives)..."; \
+ if [ "$(UNAME_S)" = "Darwin" ]; then \
+ libtool -static -o $@ $(sort $(OBJS)) $(EXTERNAL_LIBS); \
+ else \
+ { echo "CREATE $@"; \
+ for o in $(sort $(OBJS)); do echo "ADDMOD $$o"; done; \
+ for l in $(EXTERNAL_LIBS); do echo "ADDLIB $$l"; done; \
+ echo "SAVE"; echo "END"; } | $(AR) -M && $(RANLIB) $@; \
+ fi; \
+ else \
+ echo "Creating static library..."; \
+ $(AR) rcs $@ $(OBJS); \
+ fi
+
+# The embedded bundle: the fat archive plus every header the public API
+# pulls in (the public headers reference cjson, openssl, sqlite3 and ttak),
+# laid out as the compiler expects. Copy FAT_DIST_DIR onto the target board
+# and build apps with -I
/include /lib/libcwist.a; only system
+# libraries stay external.
+FAT_DIST_DIR ?= dist/cwist-fat
+fat-dist: CWIST_FAT = 1
+fat-dist: $(LIB_NAME)
+ rm -rf $(FAT_DIST_DIR)
+ mkdir -p $(FAT_DIST_DIR)/lib $(FAT_DIST_DIR)/include/cjson
+ cp $(LIB_NAME) $(FAT_DIST_DIR)/lib/
+ cp -R include/cwist $(FAT_DIST_DIR)/include/
+ cp $(CJSON_DIR)/cJSON.h $(FAT_DIST_DIR)/include/cjson/
+ cp -R $(LIBTTAK_DIR)/include/ttak $(FAT_DIST_DIR)/include/
+ cp -R $(BORINGSSL_DIR)/include/openssl $(FAT_DIST_DIR)/include/
+ cp $(SQLITE_DIR)/sqlite3.h $(FAT_DIST_DIR)/include/
$(LIBTTAK_LIB):
@echo "Building libttak..."
@@ -1570,37 +1617,37 @@ EXAMPLE_BINS = example/simple-server/simple-server \
examples: $(EXAMPLE_BINS)
example/simple-server/simple-server: $(LIB_NAME) example/simple-server/main.c
- $(CC) $(CFLAGS) -o $@ example/simple-server/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/simple-server/main.c $(LIB_NAME) $(APP_LIBS)
example/http/step-1-hello-world/hello-world: $(LIB_NAME) example/http/step-1-hello-world/main.c
- $(CC) $(CFLAGS) -o $@ example/http/step-1-hello-world/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/http/step-1-hello-world/main.c $(LIB_NAME) $(APP_LIBS)
example/http/step-2-mux-router/mux-router: $(LIB_NAME) example/http/step-2-mux-router/main.c
- $(CC) $(CFLAGS) -o $@ example/http/step-2-mux-router/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/http/step-2-mux-router/main.c $(LIB_NAME) $(APP_LIBS)
example/http/step-3-query-params/query-params: $(LIB_NAME) example/http/step-3-query-params/main.c
- $(CC) $(CFLAGS) -o $@ example/http/step-3-query-params/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/http/step-3-query-params/main.c $(LIB_NAME) $(APP_LIBS)
example/http/step-4-json-api/json-api: $(LIB_NAME) example/http/step-4-json-api/main.c
- $(CC) $(CFLAGS) -o $@ example/http/step-4-json-api/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/http/step-4-json-api/main.c $(LIB_NAME) $(APP_LIBS)
example/jwt/step-2-http-auth/jwt-auth: $(LIB_NAME) example/jwt/step-2-http-auth/main.c
- $(CC) $(CFLAGS) -o $@ example/jwt/step-2-http-auth/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/jwt/step-2-http-auth/main.c $(LIB_NAME) $(APP_LIBS)
example/cde-json-viewer/cde-json-viewer: $(LIB_NAME) example/cde-json-viewer/main.c
- $(CC) $(CFLAGS) -o $@ example/cde-json-viewer/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/cde-json-viewer/main.c $(LIB_NAME) $(APP_LIBS)
example/db/step-1-open-query/open-query: $(LIB_NAME) example/db/step-1-open-query/main.c
- $(CC) $(CFLAGS) -o $@ example/db/step-1-open-query/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/db/step-1-open-query/main.c $(LIB_NAME) $(APP_LIBS)
example/db/step-2-migrations/migrations: $(LIB_NAME) example/db/step-2-migrations/main.c
- $(CC) $(CFLAGS) -o $@ example/db/step-2-migrations/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/db/step-2-migrations/main.c $(LIB_NAME) $(APP_LIBS)
example/db/step-4-json-insert/json-insert: $(LIB_NAME) example/db/step-4-json-insert/main.c
- $(CC) $(CFLAGS) -o $@ example/db/step-4-json-insert/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/db/step-4-json-insert/main.c $(LIB_NAME) $(APP_LIBS)
example/rps-showcase/rps-showcase: $(LIB_NAME) example/rps-showcase/main.c
- $(CC) $(CFLAGS) -o $@ example/rps-showcase/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/rps-showcase/main.c $(LIB_NAME) $(APP_LIBS)
# Micro examples
MICRO_BINS = example/micro/01-hello/hello \
@@ -1648,55 +1695,55 @@ examples-check: $(LIB_NAME) $(EXAMPLE_BINS) $(MICRO_BINS)
done
example/micro/01-hello/hello: $(LIB_NAME) example/micro/01-hello/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/01-hello/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/01-hello/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/02-routes/routes: $(LIB_NAME) example/micro/02-routes/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/02-routes/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/02-routes/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/03-path-params/path-params: $(LIB_NAME) example/micro/03-path-params/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/03-path-params/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/03-path-params/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/04-query-params/query-params: $(LIB_NAME) example/micro/04-query-params/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/04-query-params/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/04-query-params/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/05-json/json: $(LIB_NAME) example/micro/05-json/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/05-json/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/05-json/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/06-orm-insert/orm-insert: $(LIB_NAME) example/micro/06-orm-insert/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/06-orm-insert/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/06-orm-insert/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/07-orm-query/orm-query: $(LIB_NAME) example/micro/07-orm-query/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/07-orm-query/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/07-orm-query/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/08-orm-update-delete/orm-update-delete: $(LIB_NAME) example/micro/08-orm-update-delete/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/08-orm-update-delete/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/08-orm-update-delete/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/09-html-builder/html-builder: $(LIB_NAME) example/micro/09-html-builder/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/09-html-builder/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/09-html-builder/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/10-static-files/static-files: $(LIB_NAME) example/micro/10-static-files/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/10-static-files/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/10-static-files/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/11-jwt-auth/jwt-auth: $(LIB_NAME) example/micro/11-jwt-auth/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/11-jwt-auth/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/11-jwt-auth/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/12-middleware/middleware: $(LIB_NAME) example/micro/12-middleware/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/12-middleware/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/12-middleware/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/13-nuke-db/nuke-db: $(LIB_NAME) example/micro/13-nuke-db/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/13-nuke-db/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/13-nuke-db/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/14-websocket/websocket: $(LIB_NAME) example/micro/14-websocket/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/14-websocket/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/14-websocket/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/15-pqc-tls/pqc-tls: $(LIB_NAME) example/micro/15-pqc-tls/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/15-pqc-tls/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/15-pqc-tls/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/16-rdbms-auto/rdbms-auto: $(LIB_NAME) example/micro/16-rdbms-auto/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/16-rdbms-auto/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/16-rdbms-auto/main.c $(LIB_NAME) $(APP_LIBS)
example/micro/17-blog-crud/blog-crud: $(LIB_NAME) example/micro/17-blog-crud/main.c
- $(CC) $(CFLAGS) -o $@ example/micro/17-blog-crud/main.c $(LIB_NAME) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ example/micro/17-blog-crud/main.c $(LIB_NAME) $(APP_LIBS)
clean-examples:
rm -f $(EXAMPLE_BINS) $(MICRO_BINS)