Skip to content

Commit 9c61716

Browse files
etrclaude
andcommitted
Merge TASK-044: SOVERSION bump and packaging (PRD §1 release strategy, DR-011)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents c18cc8f + f73aca5 commit 9c61716

8 files changed

Lines changed: 630 additions & 11 deletions

File tree

ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Version 2.0.0
2+
3+
v2.0 release. See RELEASE_NOTES.md for the full v1->v2 porting summary.
4+
SOVERSION bumped: shared library now ships as libhttpserver.so.2
5+
(Linux) / libhttpserver.2.dylib (Darwin). Parallel-installable
6+
with any prior libhttpserver.so.0/1 on disk (runtime artefacts
7+
only; dev-time .la/.a/.pc/headers are last-installer-wins by
8+
design).
9+
110
Version 0.20.0
211

312
Raised minimum C++ standard to C++20. Build now requires gcc >= 10

Makefile.am

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ endif
4141
EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh \
4242
scripts/check-examples.sh scripts/check-readme.sh scripts/check-release-notes.sh \
4343
scripts/check-doxygen.sh \
44+
scripts/check-soversion.sh scripts/check-parallel-install.sh \
4445
scripts/verify-installed-examples.sh \
4546
test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_umbrella.cpp \
4647
test/headers/consumer_post_umbrella.cpp \
@@ -328,7 +329,26 @@ check-doxygen:
328329
@echo "=== check-doxygen: enforce TASK-043 zero-warning invariant ==="
329330
@BUILD_DIR="$(abs_top_builddir)" $(top_srcdir)/scripts/check-doxygen.sh
330331

331-
.PHONY: check-headers check-install-layout check-hygiene check-examples check-readme check-release-notes check-doxygen
332+
# TASK-044: SOVERSION acceptance gate. Stages a clean DESTDIR install and
333+
# asserts that the v2.0 on-disk SONAME layout is correct (libhttpserver.so.2
334+
# on Linux / libhttpserver.2.dylib on Darwin, pkg-config --modversion 2.0.0,
335+
# correct symlink chain). NOT wired into `make check` — it does its own
336+
# `make install` and is intentionally invoked standalone to keep per-PR CI
337+
# fast. Run manually before tagging a release.
338+
check-soversion:
339+
@echo "=== check-soversion: TASK-044 SOVERSION acceptance gate ==="
340+
@BUILD_DIR="$(abs_top_builddir)" $(top_srcdir)/scripts/check-soversion.sh
341+
342+
# TASK-044: parallel-installability verification. Builds master and installs
343+
# v1 + v2 into the same DESTDIR and asserts both SONAMEd libraries coexist.
344+
# Best-effort: emits SKIP and exits 0 if the v1 source cannot be built in
345+
# this environment (toolchain mismatch, missing libtoolize, etc.).
346+
# Definitely NOT in `make check` — requires a clean v1 build of master.
347+
check-parallel-install:
348+
@echo "=== check-parallel-install: TASK-044 v1+v2 coexistence ==="
349+
@BUILD_DIR="$(abs_top_builddir)" $(top_srcdir)/scripts/check-parallel-install.sh
350+
351+
.PHONY: check-headers check-install-layout check-hygiene check-examples check-readme check-release-notes check-doxygen check-soversion check-parallel-install
332352

333353
# TASK-039: top-level convenience rule that descends into test/ to
334354
# build and run the bench binaries (see test/Makefile.am and

configure.ac

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
# Process this file with autoconf to produce a configure script.
2121

2222
AC_PREREQ(2.57)
23-
m4_define([libhttpserver_MAJOR_VERSION],[0])dnl
24-
m4_define([libhttpserver_MINOR_VERSION],[20])dnl
23+
m4_define([libhttpserver_MAJOR_VERSION],[2])dnl
24+
m4_define([libhttpserver_MINOR_VERSION],[0])dnl
2525
m4_define([libhttpserver_REVISION],[0])dnl
2626
m4_define([libhttpserver_PKG_VERSION],[libhttpserver_MAJOR_VERSION.libhttpserver_MINOR_VERSION.libhttpserver_REVISION])dnl
2727
m4_define([libhttpserver_LDF_VERSION],[libhttpserver_MAJOR_VERSION:libhttpserver_MINOR_VERSION:libhttpserver_REVISION])dnl

scripts/check-parallel-install.sh

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env bash
2+
#
3+
# check-parallel-install.sh — TASK-044 parallel-installability verification.
4+
#
5+
# This script proves that libhttpserver v1 and libhttpserver v2.0 can be
6+
# installed side-by-side into the same prefix without their shared-library
7+
# artefacts colliding. The promise is on the SONAME / runtime-library side —
8+
# v1's `libhttpserver.so.0` and v2's `libhttpserver.so.2` (Linux) or
9+
# `libhttpserver.0.dylib` and `libhttpserver.2.0.0.dylib` (Darwin) MUST
10+
# coexist after installing both into the same DESTDIR.
11+
#
12+
# The dev-time artefacts (libhttpserver.la, libhttpserver.a, the .pc file,
13+
# the headers, the bare `libhttpserver.so`/`libhttpserver.dylib` dev symlink)
14+
# DO collide. The last installer wins — this is the standard C++ library
15+
# behaviour and is intentional. We only assert the runtime SONAME duo here.
16+
#
17+
# Strategy:
18+
# 1. Build the current (v2.0) tree from $BUILD_DIR and install into
19+
# $SHARED_STAGE (a fresh DESTDIR).
20+
# 2. Use `git worktree add` (read-only, ephemeral) to materialise a master
21+
# checkout, bootstrap+configure+build it, and install it into the SAME
22+
# $SHARED_STAGE.
23+
# 3. Assert that both SONAMEd library files coexist on disk after step 2.
24+
#
25+
# Inputs (via env, all optional):
26+
# BUILD_DIR — current-tree out-of-tree build directory.
27+
# Default: $REPO_ROOT/build.
28+
# SHARED_STAGE — DESTDIR root for the parallel install.
29+
# Default: $BUILD_DIR/.parallel-stage.
30+
# MASTER_REF — git ref to use as the v1 source.
31+
# Default: master.
32+
# MASTER_WORKTREE — path for the temporary v1 worktree.
33+
# Default: $BUILD_DIR/.parallel-master-worktree.
34+
#
35+
# This script is best-effort and exits 0 with a clear SKIP message if the
36+
# v1 source tree cannot be built in this environment (e.g. glibtoolize
37+
# missing). It is NOT wired into `make check` to keep per-PR CI fast; it
38+
# lives as an opt-in `make check-parallel-install` rule.
39+
#
40+
# Acceptance:
41+
# * On Linux: $STAGE$libdir/libhttpserver.so.0 (or whatever v1 ships)
42+
# coexists with $STAGE$libdir/libhttpserver.so.2.0.0 after step 2.
43+
# * On Darwin: $STAGE$libdir/libhttpserver.0.dylib (or v1 SONAME)
44+
# coexists with $STAGE$libdir/libhttpserver.2.dylib after step 2.
45+
# (libtool's -version-number A:B:C on Mach-O emits only .A.dylib;
46+
# the .A.B.C.dylib intermediate is a Linux-only convention.)
47+
#
48+
# This script is intentionally tolerant: any failure to even produce a v1
49+
# build is treated as a SKIP, because the user-visible promise (the v2
50+
# install is well-formed; the on-disk filenames don't clash) is verified
51+
# either way.
52+
53+
set -uo pipefail
54+
55+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
56+
BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build}"
57+
SHARED_STAGE="${SHARED_STAGE:-$BUILD_DIR/.parallel-stage}"
58+
MASTER_REF="${MASTER_REF:-master}"
59+
MASTER_WORKTREE="${MASTER_WORKTREE:-$BUILD_DIR/.parallel-master-worktree}"
60+
PLATFORM="$(uname -s)"
61+
62+
skip() {
63+
echo "check-parallel-install: SKIP: $*" >&2
64+
exit 0
65+
}
66+
67+
fail() {
68+
echo "check-parallel-install: FAIL: $*" >&2
69+
exit 1
70+
}
71+
72+
pass() {
73+
echo " PASS: $*"
74+
}
75+
76+
cleanup() {
77+
# Tear down the ephemeral worktree if we created one. Don't touch
78+
# $SHARED_STAGE — the maintainer may want to inspect it.
79+
if [ -d "$MASTER_WORKTREE" ]; then
80+
# `git worktree remove --force` may exit non-zero on shallow clones;
81+
# ignore errors — the directory cleanup is sufficient for re-run safety.
82+
git -C "$REPO_ROOT" worktree remove --force "$MASTER_WORKTREE" 2>/dev/null || true
83+
rm -rf "$MASTER_WORKTREE"
84+
fi
85+
}
86+
trap cleanup EXIT
87+
88+
[ -d "$BUILD_DIR" ] || skip "BUILD_DIR=$BUILD_DIR does not exist (run ./configure first)"
89+
90+
# Resolve libdir from config.status (same trick as check-soversion.sh).
91+
CONFIG_STATUS="$BUILD_DIR/config.status"
92+
[ -x "$CONFIG_STATUS" ] || skip "$CONFIG_STATUS missing (run ./configure first)"
93+
RESOLVED_PREFIX="$("$CONFIG_STATUS" --config 2>/dev/null \
94+
| tr ' ' '\n' | grep -E "^'?--prefix=" | head -1 | sed "s/^'\?--prefix=//;s/'\$//")"
95+
[ -z "$RESOLVED_PREFIX" ] && RESOLVED_PREFIX="/usr/local"
96+
LIBDIR="$RESOLVED_PREFIX/lib"
97+
STAGE_LIB="$SHARED_STAGE$LIBDIR"
98+
99+
case "$PLATFORM" in
100+
Linux)
101+
V2_FULL_BASENAME="libhttpserver.so.2.0.0"
102+
# v1 install on disk uses the major from configure.ac's
103+
# MAJOR_VERSION — most commonly .so.0 (legacy 0.x line) or
104+
# .so.1. We don't pin the v1 SONAME here; we just require
105+
# *some* sibling file shaped like libhttpserver.so.<digit>.
106+
V1_PATTERN='^libhttpserver\.so\.[01]'
107+
;;
108+
Darwin)
109+
# libtool with -version-number on Mach-O produces only the
110+
# major-numbered .N.dylib (no .N.M.P.dylib intermediate).
111+
V2_FULL_BASENAME="libhttpserver.2.dylib"
112+
V1_PATTERN='^libhttpserver\.[01]\.dylib'
113+
;;
114+
*)
115+
fail "unsupported platform '$PLATFORM' (need Linux or Darwin)"
116+
;;
117+
esac
118+
119+
echo "=== check-parallel-install: v2 + v1 coexistence on the same DESTDIR ==="
120+
echo " BUILD_DIR : $BUILD_DIR"
121+
echo " SHARED_STAGE : $SHARED_STAGE"
122+
echo " MASTER_REF : $MASTER_REF"
123+
echo " MASTER_WORKTREE : $MASTER_WORKTREE"
124+
125+
# ---- Phase 1: install v2 into the shared stage --------------------------------
126+
rm -rf "$SHARED_STAGE"
127+
mkdir -p "$SHARED_STAGE"
128+
echo "Phase 1: installing v2 from $BUILD_DIR"
129+
if ! ( cd "$BUILD_DIR" && make install DESTDIR="$SHARED_STAGE" ) >"$SHARED_STAGE/.v2-install.log" 2>&1; then
130+
echo "---- v2 install log (tail) ----" >&2
131+
tail -40 "$SHARED_STAGE/.v2-install.log" >&2
132+
fail "v2 install into $SHARED_STAGE failed"
133+
fi
134+
pass "v2 install succeeded into $SHARED_STAGE"
135+
136+
[ -f "$STAGE_LIB/$V2_FULL_BASENAME" ] \
137+
|| fail "expected v2 library $V2_FULL_BASENAME after phase 1, got: $(ls "$STAGE_LIB" 2>/dev/null || echo '(libdir missing)')"
138+
139+
# ---- Phase 2: materialise v1 source via git worktree --------------------------
140+
echo "Phase 2: building $MASTER_REF in a temporary git worktree"
141+
142+
if ! git -C "$REPO_ROOT" rev-parse --verify "$MASTER_REF" >/dev/null 2>&1; then
143+
skip "ref '$MASTER_REF' not in this repository — cannot do automated v1 build"
144+
fi
145+
146+
# Remove a stale worktree from a previous aborted run, if any.
147+
if [ -d "$MASTER_WORKTREE" ]; then
148+
git -C "$REPO_ROOT" worktree remove --force "$MASTER_WORKTREE" 2>/dev/null || true
149+
rm -rf "$MASTER_WORKTREE"
150+
fi
151+
152+
if ! git -C "$REPO_ROOT" worktree add --detach "$MASTER_WORKTREE" "$MASTER_REF" >"$SHARED_STAGE/.worktree-add.log" 2>&1; then
153+
cat "$SHARED_STAGE/.worktree-add.log" >&2
154+
skip "git worktree add failed; cannot do automated v1 build"
155+
fi
156+
157+
# Bootstrap, configure, build, install the v1 source.
158+
echo " bootstrapping $MASTER_REF in $MASTER_WORKTREE"
159+
if ! ( cd "$MASTER_WORKTREE" && ./bootstrap ) >"$SHARED_STAGE/.v1-bootstrap.log" 2>&1; then
160+
echo " (v1 bootstrap failed; this often means glibtoolize/libtoolize is missing)"
161+
tail -20 "$SHARED_STAGE/.v1-bootstrap.log" >&2
162+
skip "v1 bootstrap failed in $MASTER_WORKTREE (treating as environment limitation)"
163+
fi
164+
165+
V1_BUILD="$MASTER_WORKTREE/build"
166+
mkdir -p "$V1_BUILD"
167+
echo " configuring $MASTER_REF"
168+
if ! ( cd "$V1_BUILD" && \
169+
CPPFLAGS="${CPPFLAGS:-} -I/opt/homebrew/include -I/opt/homebrew/opt/gnutls/include" \
170+
LDFLAGS="${LDFLAGS:-} -L/opt/homebrew/lib -L/opt/homebrew/opt/gnutls/lib" \
171+
LIBS="${LIBS:--lgnutls}" \
172+
../configure --prefix="$RESOLVED_PREFIX" ) >"$SHARED_STAGE/.v1-configure.log" 2>&1; then
173+
tail -20 "$SHARED_STAGE/.v1-configure.log" >&2
174+
skip "v1 configure failed (treating as environment limitation)"
175+
fi
176+
177+
echo " building $MASTER_REF"
178+
if ! ( cd "$V1_BUILD" && make -j4 ) >"$SHARED_STAGE/.v1-make.log" 2>&1; then
179+
tail -30 "$SHARED_STAGE/.v1-make.log" >&2
180+
skip "v1 make failed (treating as environment limitation; e.g. v1 sources may not build on this toolchain)"
181+
fi
182+
183+
echo " installing $MASTER_REF into $SHARED_STAGE"
184+
if ! ( cd "$V1_BUILD" && make install DESTDIR="$SHARED_STAGE" ) >"$SHARED_STAGE/.v1-install.log" 2>&1; then
185+
tail -30 "$SHARED_STAGE/.v1-install.log" >&2
186+
fail "v1 install into $SHARED_STAGE failed AFTER successful v1 build"
187+
fi
188+
pass "v1 ($MASTER_REF) install succeeded into shared stage"
189+
190+
# ---- Phase 3: assert coexistence ---------------------------------------------
191+
[ -f "$STAGE_LIB/$V2_FULL_BASENAME" ] \
192+
|| fail "v2 library $V2_FULL_BASENAME disappeared from $STAGE_LIB after v1 install"
193+
194+
# Look for any v1-style sibling that is NOT the v2 file we just confirmed.
195+
v1_hits="$(ls "$STAGE_LIB" 2>/dev/null | grep -E "$V1_PATTERN" | grep -v "^$V2_FULL_BASENAME\$" || true)"
196+
if [ -z "$v1_hits" ]; then
197+
fail "no v1-style library file (matching $V1_PATTERN, excluding $V2_FULL_BASENAME) found in $STAGE_LIB; install contents: $(ls "$STAGE_LIB" 2>/dev/null)"
198+
fi
199+
200+
echo " v1 artefacts coexisting with v2:"
201+
for f in $v1_hits; do
202+
echo " $f"
203+
done
204+
pass "v1 and v2 SONAMEd libraries coexist in $STAGE_LIB"
205+
206+
echo " Note: dev-time files (libhttpserver.la, libhttpserver.a, libhttpserver.pc,"
207+
echo " headers, the bare libhttpserver.so/.dylib dev symlink) are LAST-WRITER-WINS"
208+
echo " by design — see RELEASE_NOTES.md 'SOVERSION & packaging'."
209+
210+
echo " ALL PASS: parallel-installability verified"
211+
exit 0

0 commit comments

Comments
 (0)