Skip to content

Commit a934d43

Browse files
etrclaude
andcommitted
TASK-044 review cleanup: address all 5 majors and 22 of 23 minors
- scripts/lib/resolve-prefix.sh: new shared helper sourced by both gate scripts; POSIX-portable sed (no GNU \?), absolute-path guard, split pipeline for readability. Eliminates DRY violation (items 2, 3, 10, 16, 22). - check-soversion.sh: move resolve_symlink_chain() to top alongside fail/pass (item 4); add set -uo pipefail comment (item 9); tee install for streaming CI output (item 11); strict SONAME equality in A5 via sed bracket-extraction (item 27); narrow A7 grep to ^library_names= field (item 28). - check-parallel-install.sh: fix header comment Darwin name (item 6); add -e-omission comment (item 9); extract remove_master_worktree() helper for EXIT trap and stale-cleanup (item 14); rename V1_PATTERN to V1_SONAME_PATTERN with per-platform comments (items 13, 24); gate Homebrew flags on Darwin, use brew --prefix dynamically (items 8, 15, 26); dynamic make -j parallelism (item 19); Phase 3 strict exact-filename check from .la library_names field, SONAME collision detection, while IFS= read -r for v1_hits (items 1, 5, 21). - configure.ac + src/Makefile.am: move -version-number from global LDFLAGS to libhttpserver_la_LDFLAGS via LHT_LDF_VERSION AC_SUBST (item 25). - Makefile.am: add scripts/lib/resolve-prefix.sh to EXTRA_DIST. - specs/tasks/_index.md: bump Last updated to 2026-05-20 (items 12, 18). - Mark 27/28 items in review file; item 23 deferred (design decision). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 17bfe16 commit a934d43

8 files changed

Lines changed: 236 additions & 98 deletions

File tree

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh s
4242
scripts/check-examples.sh scripts/check-readme.sh scripts/check-release-notes.sh \
4343
scripts/check-doxygen.sh scripts/check-hooks-doc-spotcheck.sh \
4444
scripts/check-soversion.sh scripts/check-parallel-install.sh \
45+
scripts/lib/resolve-prefix.sh \
4546
scripts/check-complexity.sh scripts/check-duplication.sh \
4647
scripts/check-file-size.sh \
4748
scripts/verify-installed-examples.sh \

configure.ac

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,11 @@ DX_PDF_FEATURE(OFF)
323323
DX_PS_FEATURE(OFF)
324324
DX_INIT_DOXYGEN([$PACKAGE_NAME],[doxyconfig.in])
325325

326-
LDFLAGS="$LDFLAGS -version-number libhttpserver_LDF_VERSION"
326+
# Export the libtool version-number triple so src/Makefile.am can scope it
327+
# to libhttpserver_la_LDFLAGS rather than injecting it into the global LDFLAGS
328+
# (which would affect all link steps, not just the shared library).
329+
LHT_LDF_VERSION="libhttpserver_LDF_VERSION"
330+
AC_SUBST(LHT_LDF_VERSION)
327331

328332
AC_SUBST(LHT_LIBDEPS)
329333
AC_SUBST(AM_CXXFLAGS)

scripts/check-parallel-install.sh

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
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,
@@ -50,6 +50,8 @@
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.
5355
set -uo pipefail
5456

5557
REPO_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+
}
8693
trap 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.
9198
CONFIG_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"
96102
LIBDIR="$RESOLVED_PREFIX/lib"
97103
STAGE_LIB="$SHARED_STAGE$LIBDIR"
98104

99105
case "$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
;;
117126
esac
127+
# Alias for backward-compatibility with the fallback grep path below.
128+
V1_PATTERN="$V1_SONAME_PATTERN"
118129

119130
echo "=== check-parallel-install: v2 + v1 coexistence on the same DESTDIR ==="
120131
echo " BUILD_DIR : $BUILD_DIR"
@@ -144,10 +155,7 @@ if ! git -C "$REPO_ROOT" rev-parse --verify "$MASTER_REF" >/dev/null 2>&1; then
144155
fi
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

152160
if ! 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
@@ -165,17 +173,36 @@ fi
165173
V1_BUILD="$MASTER_WORKTREE/build"
166174
mkdir -p "$V1_BUILD"
167175
echo " 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
168193
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" \
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)"
175200
fi
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)"
181208
fi
@@ -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
198232
fi
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

206258
echo " Note: dev-time files (libhttpserver.la, libhttpserver.a, libhttpserver.pc,"
207259
echo " headers, the bare libhttpserver.so/.dylib dev symlink) are LAST-WRITER-WINS"

scripts/check-soversion.sh

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
# Exits non-zero on the first violation, with a clear FAIL line that names
4242
# the failing assertion.
4343

44+
# -e is intentionally omitted: every significant command uses explicit
45+
# '|| fail ...' error handling so that failures produce a clear, named
46+
# assertion message rather than a silent non-zero exit. Adding -e would
47+
# break the 'true'-guarded grep calls and confuse the intentional design.
4448
set -uo pipefail
4549

4650
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -57,6 +61,23 @@ pass() {
5761
echo " PASS: $*"
5862
}
5963

64+
# Portably resolve a chain of symlinks to the final real path.
65+
# Used instead of `readlink -f` which is not available on all platforms.
66+
resolve_symlink_chain() {
67+
local p="$1"
68+
local d
69+
local target
70+
while [ -L "$p" ]; do
71+
target="$(readlink "$p")"
72+
d="$(dirname "$p")"
73+
case "$target" in
74+
/*) p="$target" ;;
75+
*) p="$d/$target" ;;
76+
esac
77+
done
78+
printf '%s\n' "$p"
79+
}
80+
6081
# Detect the configured libdir/includedir from libtool's view of the build.
6182
# The Makefile substitutes ${prefix}, so we fish them out of config.status
6283
# (the canonical record of `./configure`'s decisions).
@@ -69,11 +90,9 @@ if [ ! -x "$CONFIG_STATUS" ]; then
6990
fail "$CONFIG_STATUS not found; run ./configure in $BUILD_DIR first"
7091
fi
7192

72-
# Resolve prefix/libdir/includedir by asking config.status. These come back
73-
# already expanded (no ${prefix} placeholders), suitable for path use.
74-
RESOLVED_PREFIX="$("$CONFIG_STATUS" --config 2>/dev/null \
75-
| tr ' ' '\n' | grep -E "^'?--prefix=" | head -1 | sed "s/^'\?--prefix=//;s/'\$//")"
76-
[ -z "$RESOLVED_PREFIX" ] && RESOLVED_PREFIX="/usr/local"
93+
# Resolve prefix by asking config.status (shared helper; see scripts/lib/).
94+
# shellcheck source=scripts/lib/resolve-prefix.sh
95+
source "$(dirname "$0")/lib/resolve-prefix.sh"
7796

7897
# libdir & includedir default to $prefix/lib and $prefix/include for our
7998
# configure setup; configure.ac doesn't override AC_PROG_LIBTOOL defaults.
@@ -89,8 +108,13 @@ echo " libdir : $LIBDIR"
89108
# ---- A1: clean install --------------------------------------------------------
90109
rm -rf "$STAGE"
91110
mkdir -p "$STAGE"
92-
( cd "$BUILD_DIR" && make install DESTDIR="$STAGE" ) >"$STAGE/.install.log" 2>&1 \
93-
|| { echo "---- install log (tail) ----" >&2; tail -40 "$STAGE/.install.log" >&2; fail "A1: 'make install DESTDIR=$STAGE' failed"; }
111+
# Use tee so CI consoles see streaming progress while the log is also captured.
112+
( cd "$BUILD_DIR" && make install DESTDIR="$STAGE" ) 2>&1 | tee "$STAGE/.install.log"
113+
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
114+
echo "---- install log (tail) ----" >&2
115+
tail -40 "$STAGE/.install.log" >&2
116+
fail "A1: 'make install DESTDIR=$STAGE' failed"
117+
fi
94118
pass "A1: staged install succeeded"
95119

96120
STAGE_LIB="$STAGE$LIBDIR"
@@ -143,22 +167,7 @@ fi
143167

144168
# A4: dev symlink — must resolve (possibly through SONAME_LINK) to FULL.
145169
[ -L "$DEV_LINK" ] || fail "A4: expected symlink at $DEV_LINK"
146-
# Use shell readlink + manual chase; some systems lack `readlink -f`.
147-
chase_symlink() {
148-
local p="$1"
149-
local d
150-
local target
151-
while [ -L "$p" ]; do
152-
target="$(readlink "$p")"
153-
d="$(dirname "$p")"
154-
case "$target" in
155-
/*) p="$target" ;;
156-
*) p="$d/$target" ;;
157-
esac
158-
done
159-
printf '%s\n' "$p"
160-
}
161-
DEV_RESOLVED="$(chase_symlink "$DEV_LINK")"
170+
DEV_RESOLVED="$(resolve_symlink_chain "$DEV_LINK")"
162171
if [ "$DEV_RESOLVED" != "$FULL" ]; then
163172
fail "A4: $DEV_LINK ultimately resolves to '$DEV_RESOLVED', expected '$FULL'"
164173
fi
@@ -172,14 +181,14 @@ case "$PLATFORM" in
172181
if [ -z "$soname_line" ]; then
173182
fail "A5: readelf -d $FULL produced no SONAME line"
174183
fi
175-
case "$soname_line" in
176-
*"[$SONAME_BASENAME]"*|*"$SONAME_BASENAME"*)
177-
pass "A5: ELF SONAME = $SONAME_BASENAME"
178-
;;
179-
*)
180-
fail "A5: ELF SONAME mismatch — got: $soname_line"
181-
;;
182-
esac
184+
# Extract the bracketed SONAME value for a strict equality check.
185+
# readelf format: (SONAME) Library soname: [libhttpserver.so.2]
186+
extracted_soname="$(printf '%s\n' "$soname_line" | sed 's/.*\[\(.*\)\].*/\1/')"
187+
if [ "$extracted_soname" = "$SONAME_BASENAME" ]; then
188+
pass "A5: ELF SONAME = $SONAME_BASENAME"
189+
else
190+
fail "A5: ELF SONAME mismatch — got '$extracted_soname', expected '$SONAME_BASENAME'"
191+
fi
183192
else
184193
echo " SKIP A5: readelf not on PATH (filename-only verification accepted)"
185194
fi
@@ -243,11 +252,12 @@ fi
243252
# ---- A7: libtool .la --------------------------------------------------------
244253
LA_FILE="$STAGE_LIB/libhttpserver.la"
245254
[ -f "$LA_FILE" ] || fail "A7: libtool control file missing: $LA_FILE"
246-
# library_names should list the SONAME symlink (Linux) or 2.dylib (Darwin)
247-
if ! grep -q "$SONAME_BASENAME" "$LA_FILE"; then
248-
fail "A7: $LA_FILE does not mention $SONAME_BASENAME"
255+
# Verify the SONAME appears specifically in the library_names= field, not just
256+
# anywhere in the file (e.g. not in a comment or dlname-only reference).
257+
if ! grep -E "^library_names=.*$SONAME_BASENAME" "$LA_FILE" >/dev/null 2>&1; then
258+
fail "A7: $LA_FILE library_names= field does not reference $SONAME_BASENAME"
249259
fi
250-
pass "A7: $(basename "$LA_FILE") references $SONAME_BASENAME"
260+
pass "A7: $(basename "$LA_FILE") library_names references $SONAME_BASENAME"
251261

252262
# ---- A8: static archive -----------------------------------------------------
253263
STATIC="$STAGE_LIB/libhttpserver.a"

scripts/lib/resolve-prefix.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
#
3+
# resolve-prefix.sh — shared helper for check-soversion.sh and
4+
# check-parallel-install.sh.
5+
#
6+
# Sourced (not executed) by both gate scripts to avoid copy-pasting the
7+
# config.status prefix-resolution pipeline in two places.
8+
#
9+
# Inputs (must be set by the caller before sourcing):
10+
# CONFIG_STATUS — absolute path to the config.status file in BUILD_DIR.
11+
# fail — a shell function that prints a message and exits non-zero.
12+
#
13+
# Outputs (set in the caller's environment):
14+
# RESOLVED_PREFIX — the configured installation prefix (e.g. /usr/local).
15+
#
16+
# After sourcing, callers typically derive:
17+
# LIBDIR="$RESOLVED_PREFIX/lib"
18+
# INCDIR="$RESOLVED_PREFIX/include"
19+
#
20+
# Design note: this helper is intentionally kept standalone (no sourcing of
21+
# further helpers) so that each gate script remains easy to invoke in
22+
# isolation without a complex dependency chain.
23+
24+
# Resolve prefix by asking config.status. The --config flag prints the
25+
# original configure arguments, one per line after splitting on spaces. We
26+
# grep for the --prefix= token, strip surrounding single-quotes (an autoconf
27+
# quoting convention), and fall back to /usr/local when absent.
28+
#
29+
# POSIX-portable sed is used: avoid GNU \? (undefined in POSIX BRE/ERE).
30+
RESOLVED_PREFIX="$("$CONFIG_STATUS" --config 2>/dev/null \
31+
| tr ' ' '\n' \
32+
| grep -E "^'?--prefix=" \
33+
| head -1 \
34+
| sed "s/^'*--prefix=//;s/'*$//")"
35+
[ -z "$RESOLVED_PREFIX" ] && RESOLVED_PREFIX="/usr/local"
36+
37+
# Guard: an absolute path is required for safe use in rm -rf / mkdir -p.
38+
case "$RESOLVED_PREFIX" in
39+
/*) ;;
40+
*) fail "resolved prefix is not absolute: $RESOLVED_PREFIX" ;;
41+
esac

specs/tasks/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# libhttpserver v2.0 — Task Plan
22

33
**Status:** Draft 1
4-
**Last updated:** 2026-05-18
4+
**Last updated:** 2026-05-20
55
**Owner:** Sebastiano Merlino
66
**Inputs:** [specs/product_specs.md](../product_specs.md), [specs/architecture/](../architecture/)
77

0 commit comments

Comments
 (0)