Fix the AKERR_USE_STDLIB=OFF build (issue #12)
All checks were successful
All checks were successful
The freestanding build (-DAKERR_USE_STDLIB=OFF) did not compile at all: bool, PATH_MAX and NULL were used unconditionally in the public header but only included under the stdlib branch, and the CMake option was pasted straight into a preprocessor definition, so a non-numeric cache spelling (-DAKERR_USE_STDLIB=ON) silently evaluated to 0. - Normalize AKERR_USE_STDLIB to a plain 1/0 in CMake before stamping it, and use a consistent '#if AKERR_USE_STDLIB' everywhere it is tested. - Include <stdbool.h>/<stddef.h> unconditionally (freestanding-safe); keep <stdlib.h>/<string.h>/<stdio.h> behind AKERR_USE_STDLIB; move <limits.h> out of the public header into src/error.c, its only user. - Define the freestanding runtime contract: AKERR_RUNTIME_HEADER must name a header providing exit, memset, snprintf, strcmp, strlen and strncpy when AKERR_USE_STDLIB is OFF, or the header #errors naming them. Add cmake/akerr_default_runtime.h, a libc-backed convenience default so this repo's own OFF build and tests work out of the box. - Route ENSURE_ERROR_READY's pool-exhaustion path through akerr_exit() instead of a direct exit(1). Deliberate behavior change: that exit code moves from 1 to AKERR_EXIT_STATUS_UNREPRESENTABLE (125), the same sentinel every other unrepresentable status already uses. Documented in docs/building.md and UPGRADING.md. Not an ABI break. - Retire PATH_MAX: AKERR_MAX_ERROR_FNAME_LENGTH is now stamped by scripts/generrno.sh from a new AKERR_MAX_ERROR_FNAME_LENGTH cache variable, defaulting to 4096 (PATH_MAX on Linux/glibc) so sizeof(akerr_ErrorContext) and the soname are unchanged. Rewrite the now-stale PATH_MAX justification in src/lock.h's feature-test-macro comment. - Fail the configure with a FATAL_ERROR, not a warning, when AKERR_USE_STDLIB=OFF and AKERR_THREADS would resolve to pthread, naming -DAKERR_THREADS=none as the fix. - Keep the generated errno table (errno.c) out of the OFF build; skip the 'errno --list' shellout in scripts/generrno.sh under OFF and stamp AKERR_LAST_ERRNO_VALUE from a new fallback cache variable (default 133, Linux's EHWPOISON) instead. - Update docs/building.md: drop the known-defect paragraph, fix sprintf -> snprintf, add size_t, drop PATH_MAX, and document the new options and the exit-code change. - Guard tests/err_errno.c's registered-name assertion behind AKERR_USE_STDLIB: akerr_init_errno() is not called when it is OFF. - Add a CI job that configures/builds/tests AKERR_USE_STDLIB=OFF with AKERR_THREADS=none, plus a compile-only -nostdinc -ffreestanding check of tests/freestanding_fixture.c against the generated header. Bump the project version to 2.0.2 (no ABI break: soname and struct layout are unchanged). Verified locally: OFF+none configures and builds clean with all tests passing; ON and the plain default build both build and pass their full test suites (37/37); OFF with the default/auto thread backend fails configure with a message naming -DAKERR_THREADS=none; sizeof(akerr_ErrorContext) is unchanged (37296 bytes, fname/function still 4096 each) versus the pre-change tree. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,13 +9,35 @@ cmake_minimum_required(VERSION 3.10)
|
||||
# 2.0.1 fixes the unhandled-error exit code, which reported success for any
|
||||
# status whose low byte was zero. It adds akerr_exit() but breaks nothing: the
|
||||
# soname is unchanged and no existing entry point changed shape.
|
||||
project(akerror VERSION 2.0.1 LANGUAGES C)
|
||||
# 2.0.2 fixes the AKERR_USE_STDLIB=OFF build, which did not compile at all
|
||||
# (issue #12): untangles the header's includes, defines the AKERR_RUNTIME_HEADER
|
||||
# freestanding contract, retires PATH_MAX in favor of the AKERR_MAX_ERROR_FNAME_LENGTH
|
||||
# build option (default unchanged, so this is not an ABI break), and fails the
|
||||
# configure instead of the build when AKERR_THREADS would resolve to pthread
|
||||
# under AKERR_USE_STDLIB=OFF. ENSURE_ERROR_READY's pool-exhaustion path now
|
||||
# calls akerr_exit() instead of exit(1) directly, which changes that exit code
|
||||
# from 1 to AKERR_EXIT_STATUS_UNREPRESENTABLE (125) -- a deliberate behavior
|
||||
# change, not an ABI break: no soname move, no entry point changed shape.
|
||||
project(akerror VERSION 2.0.2 LANGUAGES C)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(CTest)
|
||||
|
||||
set(AKERR_USE_STDLIB 1 CACHE BOOL "Use the C standard library")
|
||||
set(AKERR_MAX_ERROR_FNAME_LENGTH 4096 CACHE STRING
|
||||
"Bytes reserved for the fname/function fields of akerr_ErrorContext. Defaults to PATH_MAX on Linux/glibc, which keeps sizeof(akerr_ErrorContext) and the soname unchanged; changing it is an ABI break.")
|
||||
set(AKERR_LAST_ERRNO_VALUE_FALLBACK 133 CACHE STRING
|
||||
"AKERR_LAST_ERRNO_VALUE to stamp when AKERR_USE_STDLIB is OFF, since the freestanding build cannot shell out to 'errno --list'. Defaults to 133 (Linux's EHWPOISON).")
|
||||
# Mandatory under AKERR_USE_STDLIB=OFF: the generated header #errors at
|
||||
# compile time if it is unset when included (see include/akerror.tmpl.h). Left
|
||||
# empty here so an explicit -DAKERR_RUNTIME_HEADER=... is honored; if still
|
||||
# empty once AKERR_USE_STDLIB=OFF is known (below), it defaults to a
|
||||
# convenience header that is merely a thin, libc-backed stand-in, so this
|
||||
# repository's own OFF build and tests work without a real freestanding
|
||||
# runtime. A genuinely freestanding consumer should override this.
|
||||
set(AKERR_RUNTIME_HEADER "" CACHE STRING
|
||||
"Header providing exit, memset, snprintf, strcmp, strlen and strncpy, required when AKERR_USE_STDLIB is OFF")
|
||||
set(AKERR_COVERAGE 0 CACHE BOOL "Instrument the build with gcov coverage counters")
|
||||
set(AKERR_SANITIZE "" CACHE STRING
|
||||
"Sanitizers to build the library and tests with, e.g. thread or address,undefined")
|
||||
@@ -51,6 +73,34 @@ else()
|
||||
"AKERR_THREADS must be auto, pthread or none, not '${AKERR_THREADS}'")
|
||||
endif()
|
||||
|
||||
# Normalize the stdlib option. CMake cache booleans may be spelled ON/OFF,
|
||||
# TRUE/FALSE, 1/0, YES/NO and more; pasting AKERR_USE_STDLIB straight into a
|
||||
# preprocessor definition (as this used to) left non-numeric spellings such as
|
||||
# -DAKERR_USE_STDLIB=ON expanding to "#if ON == 1", where ON reads as an
|
||||
# undefined identifier -- silently 0. Reduce it to a plain 1 or 0 once, here.
|
||||
if(AKERR_USE_STDLIB)
|
||||
set(AKERR_USE_STDLIB_DEFINE 1)
|
||||
else()
|
||||
set(AKERR_USE_STDLIB_DEFINE 0)
|
||||
if(AKERR_RUNTIME_HEADER STREQUAL "")
|
||||
set(AKERR_RUNTIME_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/cmake/akerr_default_runtime.h")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# A freestanding build cannot be thread safe through pthreads: src/lock.h's
|
||||
# pthread backend calls into libc (pthread_mutex_init, abort) unconditionally,
|
||||
# and the freestanding runtime contract (AKERR_RUNTIME_HEADER) does not cover
|
||||
# it. Fail the configure rather than produce a library that silently links
|
||||
# libc anyway.
|
||||
if(NOT AKERR_USE_STDLIB AND AKERR_THREAD_SAFE)
|
||||
message(FATAL_ERROR
|
||||
"AKERR_USE_STDLIB=OFF is incompatible with a pthread threading "
|
||||
"backend: libakerror serializes its global state with a recursive "
|
||||
"pthread mutex, and pthreads pull in the C standard library. "
|
||||
"Configure with -DAKERR_THREADS=none to build a freestanding "
|
||||
"library instead.")
|
||||
endif()
|
||||
|
||||
# Size of the private status-name hash table. Must be a power of two; usable
|
||||
# capacity is 75% of it (src/error.c asserts both). The host's errno list
|
||||
# consumes part of that at akerr_init() time, so the remainder is what all
|
||||
@@ -154,6 +204,9 @@ add_custom_command(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${GENERATED_DIR}
|
||||
${AKERR_THREAD_SAFE}
|
||||
${AKERR_USE_STDLIB_DEFINE}
|
||||
${AKERR_LAST_ERRNO_VALUE_FALLBACK}
|
||||
${AKERR_MAX_ERROR_FNAME_LENGTH}
|
||||
DEPENDS ${SCRIPT} ${INFILE} ${GENERATED_THREAD_STAMP}
|
||||
VERBATIM
|
||||
)
|
||||
@@ -165,9 +218,20 @@ add_custom_target(akerror_generated
|
||||
DEPENDS ${GENERATED_ERRNO_C} ${GENERATED_AKERROR_H}
|
||||
)
|
||||
|
||||
# The generated errno table is produced by shelling out to `errno --list`
|
||||
# (moreutils) and #include <errno.h>, neither of which is freestanding-safe.
|
||||
# It is also useless there: akerr_init_errno() is only ever called under
|
||||
# AKERR_USE_STDLIB (see src/error.c), so a freestanding build does not compile
|
||||
# or link it into the library at all.
|
||||
if(AKERR_USE_STDLIB)
|
||||
set(AKERR_ERRNO_SOURCES ${GENERATED_ERRNO_C})
|
||||
else()
|
||||
set(AKERR_ERRNO_SOURCES)
|
||||
endif()
|
||||
|
||||
add_library(akerror SHARED
|
||||
src/error.c
|
||||
${GENERATED_ERRNO_C}
|
||||
${AKERR_ERRNO_SOURCES}
|
||||
)
|
||||
add_dependencies(akerror akerror_generated)
|
||||
|
||||
@@ -189,12 +253,25 @@ else()
|
||||
set(AKERR_THREADS_DEFINE AKERR_THREADS_NONE=1)
|
||||
endif()
|
||||
|
||||
# PUBLIC and unconditional: the generated header #errors at compile time if
|
||||
# AKERR_USE_STDLIB is OFF and this is not defined, and that check runs for any
|
||||
# translation unit that includes the header -- the library's own sources as
|
||||
# much as a consumer's. Harmless (and unused) when AKERR_USE_STDLIB is ON.
|
||||
if(NOT AKERR_USE_STDLIB)
|
||||
set(AKERR_RUNTIME_HEADER_DEFINE "AKERR_RUNTIME_HEADER=\"${AKERR_RUNTIME_HEADER}\"")
|
||||
else()
|
||||
set(AKERR_RUNTIME_HEADER_DEFINE "")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(akerror
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB}
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB_DEFINE}
|
||||
PRIVATE AKERR_STATUS_NAME_SLOTS=${AKERR_STATUS_NAME_SLOTS}
|
||||
PRIVATE AKERR_MAX_RESERVED_STATUS_RANGES=${AKERR_MAX_RESERVED_STATUS_RANGES}
|
||||
PRIVATE ${AKERR_THREADS_DEFINE}
|
||||
)
|
||||
if(AKERR_RUNTIME_HEADER_DEFINE)
|
||||
target_compile_definitions(akerror PUBLIC ${AKERR_RUNTIME_HEADER_DEFINE})
|
||||
endif()
|
||||
|
||||
if(AKERR_THREAD_SAFE)
|
||||
target_link_libraries(akerror PRIVATE Threads::Threads)
|
||||
@@ -216,18 +293,21 @@ akerr_instrument_for_sanitizers(akerror)
|
||||
# weakens the production library.
|
||||
add_library(akerror_init_failure SHARED
|
||||
src/error.c
|
||||
${GENERATED_ERRNO_C}
|
||||
${AKERR_ERRNO_SOURCES}
|
||||
)
|
||||
add_dependencies(akerror_init_failure akerror_generated)
|
||||
target_include_directories(akerror_init_failure PUBLIC
|
||||
${GENERATED_DIR}/include
|
||||
)
|
||||
target_compile_definitions(akerror_init_failure
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB}
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB_DEFINE}
|
||||
PRIVATE AKERR_STATUS_NAME_SLOTS=8
|
||||
PRIVATE AKERR_MAX_RESERVED_STATUS_RANGES=0
|
||||
PRIVATE ${AKERR_THREADS_DEFINE}
|
||||
)
|
||||
if(AKERR_RUNTIME_HEADER_DEFINE)
|
||||
target_compile_definitions(akerror_init_failure PUBLIC ${AKERR_RUNTIME_HEADER_DEFINE})
|
||||
endif()
|
||||
if(AKERR_THREAD_SAFE)
|
||||
target_link_libraries(akerror_init_failure PRIVATE Threads::Threads)
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user