@@ -38,7 +38,134 @@ endif
3838
3939endif
4040
41- EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG ) scripts/extract-release-notes.sh scripts/validate-version.sh
41+ EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG ) scripts/extract-release-notes.sh scripts/validate-version.sh \
42+ test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_umbrella.cpp \
43+ test/headers/consumer_post_umbrella.cpp
44+
45+ # ---------------------------------------------------------------------------
46+ # Header-hygiene checks (TASK-002)
47+ #
48+ # check-headers verifies that the public/private header gates are wired up
49+ # correctly:
50+ # A.1 a consumer including a public header WITHOUT the umbrella must hit the
51+ # inclusion-gate #error.
52+ # A.2 a consumer including a detail header WITHOUT HTTPSERVER_COMPILATION
53+ # must hit the gate.
54+ # A.3 a consumer including only the umbrella, WITHOUT HTTPSERVER_COMPILATION,
55+ # must compile cleanly.
56+ #
57+ # The CXX invocations below override CXXFLAGS to '' so that
58+ # -DHTTPSERVER_COMPILATION (injected by configure.ac into CXXFLAGS for the
59+ # library and test build) does NOT leak into the consumer-style compile. We
60+ # still pass -std=c++20 explicitly because libhttpserver requires C++20.
61+ # ---------------------------------------------------------------------------
62+
63+ # Compose CXX with: explicit -std, the source/build include search paths used by
64+ # the library, and $(CPPFLAGS) (e.g., -I/opt/homebrew/include from configure).
65+ # Deliberately omit $(CXXFLAGS), $(AM_CPPFLAGS), and any per-target CPPFLAGS so
66+ # that -DHTTPSERVER_COMPILATION (set in src/ and test/ AM_CPPFLAGS) cannot
67+ # leak into the consumer-style compile. A true consumer never has that macro.
68+ CHECK_HEADERS_CXX = $(CXX ) -std=c++20 -I$(top_builddir ) -I$(top_srcdir ) /src -I$(top_srcdir ) /src/httpserver $(CPPFLAGS )
69+ CHECK_HEADERS_GATE_MSG = Only <httpserver.hpp> or <httpserverpp> can be included directly
70+
71+ check-headers :
72+ @echo " === check-headers A.1: direct public-header include must fail ==="
73+ @if $(CHECK_HEADERS_CXX ) -c $(top_srcdir ) /test/headers/consumer_direct.cpp -o /dev/null 2> check-headers-A1.log; then \
74+ echo " FAIL: consumer_direct.cpp compiled but should have errored" ; \
75+ cat check-headers-A1.log; \
76+ rm -f check-headers-A1.log; \
77+ exit 1; \
78+ fi
79+ @if ! grep -q " $( CHECK_HEADERS_GATE_MSG) " check-headers-A1.log; then \
80+ echo " FAIL: consumer_direct.cpp failed but not for the gate reason" ; \
81+ cat check-headers-A1.log; \
82+ rm -f check-headers-A1.log; \
83+ exit 1; \
84+ fi
85+ @rm -f check-headers-A1.log
86+ @echo " PASS: A.1 gate fired as expected"
87+ @echo " === check-headers A.2: direct detail-header include must fail ==="
88+ @if $(CHECK_HEADERS_CXX ) -c $(top_srcdir ) /test/headers/consumer_detail.cpp -o /dev/null 2> check-headers-A2.log; then \
89+ echo " FAIL: consumer_detail.cpp compiled but should have errored" ; \
90+ cat check-headers-A2.log; \
91+ rm -f check-headers-A2.log; \
92+ exit 1; \
93+ fi
94+ @if ! grep -q " $( CHECK_HEADERS_GATE_MSG) " check-headers-A2.log; then \
95+ echo " FAIL: consumer_detail.cpp failed but not for the gate reason" ; \
96+ cat check-headers-A2.log; \
97+ rm -f check-headers-A2.log; \
98+ exit 1; \
99+ fi
100+ @rm -f check-headers-A2.log
101+ @echo " PASS: A.2 gate fired as expected"
102+ @echo " === check-headers A.3: umbrella include must succeed ==="
103+ @if ! $(CHECK_HEADERS_CXX ) -c $(top_srcdir ) /test/headers/consumer_umbrella.cpp -o consumer_umbrella.check.o 2> check-headers-A3.log; then \
104+ echo " FAIL: consumer_umbrella.cpp did not compile" ; \
105+ cat check-headers-A3.log; \
106+ rm -f check-headers-A3.log consumer_umbrella.check.o; \
107+ exit 1; \
108+ fi
109+ @rm -f check-headers-A3.log consumer_umbrella.check.o
110+ @echo " PASS: A.3 umbrella compiled cleanly"
111+ @echo " === check-headers A.4: post-umbrella direct include must fail ==="
112+ @if $(CHECK_HEADERS_CXX ) -c $(top_srcdir ) /test/headers/consumer_post_umbrella.cpp -o /dev/null 2> check-headers-A4.log; then \
113+ echo " FAIL: consumer_post_umbrella.cpp compiled but should have errored" ; \
114+ cat check-headers-A4.log; \
115+ rm -f check-headers-A4.log; \
116+ exit 1; \
117+ fi
118+ @if ! grep -q " $( CHECK_HEADERS_GATE_MSG) " check-headers-A4.log; then \
119+ echo " FAIL: consumer_post_umbrella.cpp failed but not for the gate reason" ; \
120+ cat check-headers-A4.log; \
121+ rm -f check-headers-A4.log; \
122+ exit 1; \
123+ fi
124+ @rm -f check-headers-A4.log
125+ @echo " PASS: A.4 umbrella does not leak _HTTPSERVER_HPP_INSIDE_"
126+
127+ # check-install-layout asserts that `make install DESTDIR=$(STAGE)` produces
128+ # a public include tree with NO `details/` directory and NO `*_impl.hpp` files.
129+ # This protects the public/private split as described in TASK-002 / DR-002.
130+ CHECK_INSTALL_STAGE = $(abs_top_builddir ) /.install-stage
131+
132+ check-install-layout :
133+ @echo " === check-install-layout: staged install must hide details/ and *_impl.hpp ==="
134+ @rm -rf $(CHECK_INSTALL_STAGE )
135+ @$(MAKE ) $(AM_MAKEFLAGS ) install DESTDIR=$(CHECK_INSTALL_STAGE ) > check-install.log 2>&1 || { \
136+ echo " FAIL: staged install failed" ; \
137+ cat check-install.log; \
138+ rm -f check-install.log; \
139+ rm -rf $(CHECK_INSTALL_STAGE ) ; \
140+ exit 1; \
141+ }
142+ @rm -f check-install.log
143+ @leaked_details=` find $( CHECK_INSTALL_STAGE) -type d -name details 2> /dev/null` ; \
144+ if test -n " $$ leaked_details" ; then \
145+ echo " FAIL: details/ directory leaked into install:" ; \
146+ echo " $$ leaked_details" ; \
147+ rm -rf $(CHECK_INSTALL_STAGE ) ; \
148+ exit 1; \
149+ fi
150+ @leaked_impl=` find $( CHECK_INSTALL_STAGE) -name ' *_impl.hpp' 2> /dev/null` ; \
151+ if test -n " $$ leaked_impl" ; then \
152+ echo " FAIL: *_impl.hpp file leaked into install:" ; \
153+ echo " $$ leaked_impl" ; \
154+ rm -rf $(CHECK_INSTALL_STAGE ) ; \
155+ exit 1; \
156+ fi
157+ @umbrella_count=` find $( CHECK_INSTALL_STAGE) -name ' httpserver.hpp' | wc -l | tr -d ' ' ` ; \
158+ if test " $$ umbrella_count" ! = " 1" ; then \
159+ echo " FAIL: expected exactly 1 installed httpserver.hpp, got $$ umbrella_count" ; \
160+ rm -rf $(CHECK_INSTALL_STAGE ) ; \
161+ exit 1; \
162+ fi
163+ @rm -rf $(CHECK_INSTALL_STAGE )
164+ @echo " PASS: staged install layout is clean"
165+
166+ check-local : check-headers check-install-layout
167+
168+ .PHONY : check-headers check-install-layout
42169
43170MOSTLYCLEANFILES = $(DX_CLEANFILES ) *.gcda *.gcno *.gcov
44171DISTCLEANFILES = DIST_REVISION
0 commit comments