Fix the AKERR_USE_STDLIB=OFF build (issue #12)
All checks were successful
libakerror CI Build / cmake_build_freestanding (push) Successful in 2m51s
libakerror CI Build / coverage (push) Successful in 2m51s
libakerror CI Build / cmake_build (push) Successful in 2m55s
libakerror CI Build / mutation_test (push) Successful in 47m2s

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:
2026-08-05 10:29:39 -04:00
parent 11b6308eff
commit 983ecf31c9
12 changed files with 352 additions and 45 deletions

View File

@@ -9,13 +9,22 @@ either of those.
| Option | Default | What it does |
| ------ | ------- | ------------ |
| `AKERR_THREADS` | `auto` | Threading backend: `auto`, `pthread`, or `none`. `auto` takes POSIX threads and **fails the configure** if it cannot find them. See [Building single threaded](thread-safety.md#building-single-threaded). |
| `AKERR_THREADS` | `auto` | Threading backend: `auto`, `pthread`, or `none`. `auto` takes POSIX threads and **fails the configure** if it cannot find them. See [Building single threaded](thread-safety.md#building-single-threaded). **Must be `none` when `AKERR_USE_STDLIB` is `OFF`** — the pthread backend calls into libc, and the configure fails otherwise. |
| `AKERR_USE_STDLIB` | `ON` | Link against the C standard library. See [Dependencies](#dependencies) for what you must supply instead when this is `OFF`. |
| `AKERR_RUNTIME_HEADER` | *(empty)* | **Mandatory when `AKERR_USE_STDLIB` is `OFF`.** Header providing `exit`, `memset`, `snprintf`, `strcmp`, `strlen` and `strncpy`; the generated header `#error`s at compile time if it is unset. Defaults to a thin, libc-backed convenience header (`cmake/akerr_default_runtime.h`) so this repository's own `OFF` build and test suite work without a real freestanding runtime — a genuinely freestanding consumer should override it with their own header. |
| `AKERR_MAX_ERROR_FNAME_LENGTH` | `4096` | 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. |
| `AKERR_LAST_ERRNO_VALUE_FALLBACK` | `133` | `AKERR_LAST_ERRNO_VALUE` to stamp when `AKERR_USE_STDLIB` is `OFF`, since that configuration cannot shell out to `errno --list`. Defaults to 133 (Linux's `EHWPOISON`). |
| `AKERR_STATUS_NAME_SLOTS` | `4096` | Slots in the status-name table; 75% of it is usable. |
| `AKERR_MAX_RESERVED_STATUS_RANGES` | `64` | How many status ranges may be reserved in one process. |
| `AKERR_SANITIZE` | *(empty)* | Sanitizer list applied to the library and the tests, e.g. `thread` or `address,undefined`. |
| `AKERR_COVERAGE` | `OFF` | Instrument the library with gcov counters. |
**Behavior change (2.0.2):** pool exhaustion inside `ENSURE_ERROR_READY` (every
context-producing macro goes through it) used to call `exit(1)` directly. It
now calls `akerr_exit(AKERR_EXIT_STATUS_UNREPRESENTABLE)`, so a process that
runs out of pool slots exits **125** instead of **1**. See
[UPGRADING.md](../UPGRADING.md).
The two capacity options are applied `PRIVATE`: the tables live entirely in
`src/error.c`, so raising them never changes anything a consumer can see. See
[UPGRADING.md](../UPGRADING.md) for what happens when you exhaust them.
@@ -24,40 +33,59 @@ The two capacity options are applied `PRIVATE`: the tables live entirely in
The build process relies upon `scripts/generrno.sh` which performs the following:
1. Executes `errno --list` and gathers up the output
1. Templates `include/akerror.tmpl.h` into `include/akerror.h` to set the `AKERR_LAST_ERRNO_VALUE` equal to the highest integer defined by `errno`
2. Generates `src/errno.c` which contains a function called by `akerr_init` which initializes all of the status names for the previously defined values of `errno`.
1. When `AKERR_USE_STDLIB` is `ON`: executes `errno --list` and gathers up the
output. When it is `OFF`, this is skipped entirely (`errno --list` needs
moreutils and `<errno.h>`, neither freestanding-safe) and
`AKERR_LAST_ERRNO_VALUE` is taken from the `AKERR_LAST_ERRNO_VALUE_FALLBACK`
cache variable instead.
1. Templates `include/akerror.tmpl.h` into `include/akerror.h` to set
`AKERR_LAST_ERRNO_VALUE`, `AKERR_THREAD_SAFE`, and
`AKERR_MAX_ERROR_FNAME_LENGTH`.
2. Generates `src/errno.c`, which contains a function called by `akerr_init`
that initializes all of the status names for the previously defined values
of `errno`. Under `AKERR_USE_STDLIB=OFF` this file is a stub, and CMake does
not compile it into the library at all — `akerr_init_errno()` is never
called in that configuration.
Neither output is meant to be edited. Change the template or the generator.
Neither generated output is meant to be edited. Change the template or the generator.
## Dependencies
This library depends upon `stdlib`, and upon POSIX threads unless it is built
with `-DAKERR_THREADS=none` (see [Thread safety](thread-safety.md)). If you don't want to link against stdlib, you must modify the library code to include headers and link against a library that provides the following:
with `-DAKERR_THREADS=none` (see [Thread safety](thread-safety.md)). If you
don't want to link against stdlib, build with `-DAKERR_USE_STDLIB=OFF` (which
requires `-DAKERR_THREADS=none` — see the options table above) and supply a
header, via the `AKERR_RUNTIME_HEADER` cache variable, that provides:
- `memset` function
- `strncpy` function
- `strlen` function
- `strcmp` function
- `sprintf` function
- `snprintf` function
- `exit` function
- `bool` type
- `NULL` type
- `size_t` type
- `INT_MAX` constant
- `PATH_MAX` constant
`<stdbool.h>` and `<stddef.h>` — which give you `bool`/`NULL`/`size_t` — are
included unconditionally by the public header regardless of
`AKERR_USE_STDLIB`, since both are freestanding-safe. `AKERR_RUNTIME_HEADER` is
mandatory in this configuration: the generated header `#error`s at compile
time if it is unset.
... then you can compile it thusly:
```
cmake -S . -B build -DAKERR_USE_STDLIB=OFF
cmake -S . -B build -DAKERR_USE_STDLIB=OFF -DAKERR_THREADS=none \
-DAKERR_RUNTIME_HEADER=/path/to/your/runtime.h
cmake --build build
cmake --install build
```
**Known defect:** that configuration does not currently compile. `bool`,
`PATH_MAX` and `NULL` are used unconditionally but only included under the
stdlib branch, so the header's includes need untangling before
`-DAKERR_USE_STDLIB=OFF` builds. The list above still states what a replacement
must provide. That configuration does not currently compile -- `bool`, `PATH_MAX` and `NULL`
are used unconditionally but included only under the stdlib branch. See
[issue #12](https://source.starfort.tech/andrew/libakerror/issues/12).
If you omit `-DAKERR_RUNTIME_HEADER`, the build falls back to a convenience
header backed by the host's own libc (see the options table above), so the
`OFF` configuration still builds and its test suite still runs on an ordinary
hosted machine — useful for exercising the freestanding code paths without a
real freestanding runtime, but not what an actually freestanding consumer
wants. Supply your own header to get the real thing.