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)