66# installed side-by-side into the same prefix without their shared-library
77# artefacts colliding. The promise is on the SONAME / runtime-library side —
88# 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
9+ # `libhttpserver.0.dylib` and `libhttpserver.2.dylib` (Darwin) MUST
1010# coexist after installing both into the same DESTDIR.
1111#
1212# The dev-time artefacts (libhttpserver.la, libhttpserver.a, the .pc file,
5050# install is well-formed; the on-disk filenames don't clash) is verified
5151# either way.
5252
53+ # -e is intentionally omitted: every significant command uses explicit
54+ # '|| fail/skip' error handling for clear diagnostic messages.
5355set -uo pipefail
5456
5557REPO_ROOT=" $( cd " $( dirname " $0 " ) /.." && pwd) "
@@ -73,48 +75,57 @@ pass() {
7375 echo " PASS: $* "
7476}
7577
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.
78+ remove_master_worktree () {
79+ # Tear down the ephemeral worktree if it exists. Used by both the
80+ # pre-add stale-cleanup guard and the EXIT trap.
81+ # `git worktree remove --force` may exit non-zero on shallow clones;
82+ # ignore errors — the directory cleanup is sufficient for re-run safety.
7983 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.
8284 git -C " $REPO_ROOT " worktree remove --force " $MASTER_WORKTREE " 2> /dev/null || true
8385 rm -rf " $MASTER_WORKTREE "
8486 fi
8587}
88+
89+ cleanup () {
90+ # Don't touch $SHARED_STAGE — the maintainer may want to inspect it.
91+ remove_master_worktree
92+ }
8693trap cleanup EXIT
8794
8895[ -d " $BUILD_DIR " ] || skip " BUILD_DIR=$BUILD_DIR does not exist (run ./configure first)"
8996
90- # Resolve libdir from config.status (same trick as check-soversion.sh) .
97+ # Resolve libdir from config.status via the shared helper .
9198CONFIG_STATUS=" $BUILD_DIR /config.status"
9299[ -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"
100+ # shellcheck source=scripts/lib/resolve-prefix.sh
101+ source " $( dirname " $0 " ) /lib/resolve-prefix.sh"
96102LIBDIR=" $RESOLVED_PREFIX /lib"
97103STAGE_LIB=" $SHARED_STAGE$LIBDIR "
98104
99105case " $PLATFORM " in
100106 Linux)
101107 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]'
108+ # V1_SONAME_PATTERN: fallback pattern when the exact v1 SONAME cannot
109+ # be read from the .la file (see Phase 3 below). [01] covers:
110+ # .so.0 — the legacy 0.x line (master MAJOR_VERSION=0)
111+ # .so.1 — a hypothetical 1.x bump (not shipped but possible)
112+ # The exact v1 SONAME is always preferred; this pattern is a safety net.
113+ V1_SONAME_PATTERN=' ^libhttpserver\.so\.[01]'
107114 ;;
108115 Darwin)
109116 # libtool with -version-number on Mach-O produces only the
110117 # major-numbered .N.dylib (no .N.M.P.dylib intermediate).
111118 V2_FULL_BASENAME=" libhttpserver.2.dylib"
112- V1_PATTERN=' ^libhttpserver\.[01]\.dylib'
119+ # V1_SONAME_PATTERN: fallback pattern — matches .0.dylib (legacy 0.x)
120+ # or .1.dylib (hypothetical 1.x). Same rationale as Linux above.
121+ V1_SONAME_PATTERN=' ^libhttpserver\.[01]\.dylib'
113122 ;;
114123 * )
115124 fail " unsupported platform '$PLATFORM ' (need Linux or Darwin)"
116125 ;;
117126esac
127+ # Alias for backward-compatibility with the fallback grep path below.
128+ V1_PATTERN=" $V1_SONAME_PATTERN "
118129
119130echo " === check-parallel-install: v2 + v1 coexistence on the same DESTDIR ==="
120131echo " BUILD_DIR : $BUILD_DIR "
@@ -144,10 +155,7 @@ if ! git -C "$REPO_ROOT" rev-parse --verify "$MASTER_REF" >/dev/null 2>&1; then
144155fi
145156
146157# 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
158+ remove_master_worktree
151159
152160if ! git -C " $REPO_ROOT " worktree add --detach " $MASTER_WORKTREE " " $MASTER_REF " > " $SHARED_STAGE /.worktree-add.log" 2>&1 ; then
153161 cat " $SHARED_STAGE /.worktree-add.log" >&2
165173V1_BUILD=" $MASTER_WORKTREE /build"
166174mkdir -p " $V1_BUILD "
167175echo " configuring $MASTER_REF "
176+ # Build Darwin-specific include/lib hints using the active Homebrew prefix
177+ # (if brew is on PATH) so that arm64 (/opt/homebrew) and x86_64 (/usr/local)
178+ # Homebrew installs are both handled correctly. On Linux these variables
179+ # remain empty and configure falls back to its own search paths.
180+ _HOMEBREW_CPPFLAGS=" "
181+ _HOMEBREW_LDFLAGS=" "
182+ if [ " $PLATFORM " = " Darwin" ]; then
183+ if command -v brew > /dev/null 2>&1 ; then
184+ _BREW_PREFIX=" $( brew --prefix 2> /dev/null || true) "
185+ else
186+ _BREW_PREFIX=" /opt/homebrew" # best-effort fallback for arm64
187+ fi
188+ if [ -n " $_BREW_PREFIX " ]; then
189+ _HOMEBREW_CPPFLAGS=" -I${_BREW_PREFIX} /include -I${_BREW_PREFIX} /opt/gnutls/include"
190+ _HOMEBREW_LDFLAGS=" -L${_BREW_PREFIX} /lib -L${_BREW_PREFIX} /opt/gnutls/lib"
191+ fi
192+ fi
168193if ! ( 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 " \
194+ CPPFLAGS=" ${CPPFLAGS:- } ${_HOMEBREW_CPPFLAGS} " \
195+ LDFLAGS=" ${LDFLAGS:- } ${_HOMEBREW_LDFLAGS} " \
171196 LIBS=" ${LIBS:- -lgnutls} " \
172197 ../configure --prefix=" $RESOLVED_PREFIX " ) > " $SHARED_STAGE /.v1-configure.log" 2>&1 ; then
173198 tail -20 " $SHARED_STAGE /.v1-configure.log" >&2
174199 skip " v1 configure failed (treating as environment limitation)"
175200fi
176201
177- echo " building $MASTER_REF "
178- if ! ( cd " $V1_BUILD " && make -j4 ) > " $SHARED_STAGE /.v1-make.log" 2>&1 ; then
202+ # Use all available cores; mirror what the parent make would use.
203+ _NJOBS=" $( nproc 2> /dev/null || sysctl -n hw.logicalcpu 2> /dev/null || echo 4) "
204+ echo " building $MASTER_REF (make -j${_NJOBS} )"
205+ if ! ( cd " $V1_BUILD " && make -j" ${_NJOBS} " ) > " $SHARED_STAGE /.v1-make.log" 2>&1 ; then
179206 tail -30 " $SHARED_STAGE /.v1-make.log" >&2
180207 skip " v1 make failed (treating as environment limitation; e.g. v1 sources may not build on this toolchain)"
181208fi
@@ -191,17 +218,42 @@ pass "v1 ($MASTER_REF) install succeeded into shared stage"
191218[ -f " $STAGE_LIB /$V2_FULL_BASENAME " ] \
192219 || fail " v2 library $V2_FULL_BASENAME disappeared from $STAGE_LIB after v1 install"
193220
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) "
221+ # Derive the expected v1 runtime library basename from the v1 build's .la
222+ # file (the `library_names` field), falling back to the broad V1_PATTERN
223+ # grep when the .la is absent (older libtool layouts).
224+ V1_LA=" $V1_BUILD /.libs/libhttpserver.la"
225+ V1_EXPECTED_BASENAME=" "
226+ if [ -f " $V1_LA " ]; then
227+ # `library_names='libhttpserver.so.0.x.y libhttpserver.so.0 libhttpserver.so'`
228+ # The last token in the library_names value is the dev link; the first is
229+ # the versioned runtime file. Extract the first token.
230+ _raw=" $( grep ' ^library_names=' " $V1_LA " | head -1 | sed " s/^library_names='*//;s/'*$//" ) "
231+ V1_EXPECTED_BASENAME=" ${_raw%% * } " # first whitespace-delimited token
198232fi
199233
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 "
234+ if [ -n " $V1_EXPECTED_BASENAME " ]; then
235+ # Strict check: the exact filename must appear in $STAGE_LIB and must
236+ # differ from V2_FULL_BASENAME (ensures they coexist, not just that v2
237+ # is present).
238+ if [ " $V1_EXPECTED_BASENAME " = " $V2_FULL_BASENAME " ]; then
239+ fail " v1 and v2 appear to have the SAME versioned filename ($V1_EXPECTED_BASENAME ); SONAME collision detected"
240+ fi
241+ [ -f " $STAGE_LIB /$V1_EXPECTED_BASENAME " ] \
242+ || fail " expected v1 library '$V1_EXPECTED_BASENAME ' (from $V1_LA ) not found in $STAGE_LIB ; install contents: $( ls " $STAGE_LIB " 2> /dev/null) "
243+ echo " v1 artefact coexisting with v2: $V1_EXPECTED_BASENAME "
244+ pass " v1 ($V1_EXPECTED_BASENAME ) and v2 ($V2_FULL_BASENAME ) SONAMEd libraries coexist in $STAGE_LIB "
245+ else
246+ # Fallback: broad pattern-match (V1_PATTERN covers .so.0, .so.1, etc.).
247+ v1_hits=" $( ls " $STAGE_LIB " 2> /dev/null | grep -E " $V1_PATTERN " | grep -v " ^$V2_FULL_BASENAME \$ " || true) "
248+ if [ -z " $v1_hits " ]; then
249+ 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) "
250+ fi
251+ echo " v1 artefacts coexisting with v2:"
252+ while IFS= read -r f; do
253+ echo " $f "
254+ done <<< " $v1_hits"
255+ pass " v1 and v2 SONAMEd libraries coexist in $STAGE_LIB "
256+ fi
205257
206258echo " Note: dev-time files (libhttpserver.la, libhttpserver.a, libhttpserver.pc,"
207259echo " headers, the bare libhttpserver.so/.dylib dev symlink) are LAST-WRITER-WINS"
0 commit comments