Found by building a consumer against a fresh install: an installed libakstdlib was not usable. akstdlibConfig.cmake calls find_dependency(akerror), and the top-level build pulls the submodule in EXCLUDE_FROM_ALL, so `cmake --install` on this project installs only this project and leaves that dependency unresolvable. CI had been hiding it by installing libakerror@main in a separate step -- a different libakerror from the one actually compiled, which is the inconsistency TODO.md 2.3 recorded and the previous commit removed. Removing it exposed the real gap. CI now installs deps/libakerror, the same commit the top-level build compiles, so there is one libakerror in play and the install is consumable. tests/consumer/ is the check that would have caught it: a standalone project, configured against CMAKE_PREFIX_PATH rather than as part of this build, because being part of this build is exactly what would let it pass without testing anything. It exercises what only an install has -- find_package with a version request against the generated version file, find_dependency(akerror) resolving, and the exported akstdlib::akstdlib target -- and touches one function from each of the four sources, so a library installed with a source file missing from its link line fails there rather than in the next consumer to find it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.4 KiB
Repository Guidelines
Project Structure & Module Organization
libakstdlib is a C shared library that wraps libc calls and data structures in
libakerror error contexts.
Public API declarations live in include/akstdlib.h, which is also where the
Doxygen documentation lives -- the header is the contract, the sources carry the
reasoning. The implementation is split by domain:
| File | Covers |
|---|---|
src/stdlib.c |
memory, formatted output, string-to-number, realpath, djb2, and the list/tree traversal entry points |
src/string.c |
the string.h surface |
src/stream.c |
stdio.h beyond open/read/write/close |
src/collections.c |
list and tree operations, hash map, string buffer, FNV-1a |
src/aksl_internal.h |
shared internals; not installed, not public |
Tests are one-file CTest executables under tests/test_<name>.c, with shared
helpers in tests/aksl_capture.h. tests/negative/ holds sources that must
fail to compile, and tests/consumer/ is a standalone project built against
the installed package rather than the build tree -- see the testing section
below. CMake package templates are
in cmake/ and akstdlib.pc.in. The vendored dependency is deps/libakerror;
update it as a submodule rather than editing generated files under build/,
build-asan/ or build-coverage/.
Build, Test, and Development Commands
Initialize dependencies before a fresh build:
git submodule update --init --recursive
cmake -S . -B build
cmake --build build
Run the normal suite with ctest --test-dir build --output-on-failure. Use the
instrumented build for memory and undefined-behavior checks:
cmake -S . -B build-asan -DAKSL_SANITIZE=ON
cmake --build build-asan
ctest --test-dir build-asan --output-on-failure
For coverage, configure a third tree; the report is part of that suite (the
coverage_reset / coverage_report CTest entries) and also lands in
build-coverage/coverage-summary.txt:
cmake -S . -B build-coverage -DAKSL_COVERAGE=ON
cmake --build build-coverage --target coverage
Run cmake --build build --target mutation only when you need the slower
mutation harness, and cmake --build build --target docs to regenerate the API
documentation -- that one fails if any public function, parameter or return value
is undocumented, so it is a check as well as a generator.
.githooks/pre-push runs the default build, the sanitizer build and the
documentation check before a push; enable it with
git config core.hooksPath .githooks. rebuild.sh installs to
/home/andrew/local and removes the local build directory, so treat it as a
local convenience script.
Coding Style & Naming Conventions
Use C with 4-space indentation; existing files sometimes use tabs for continued
statements, so match the surrounding block. Public symbols use the aksl_
prefix, structs use aksl_<Name>, and tests use test_<feature>.c plus static
test_<case> functions. Preserve the akerr_ErrorContext AKERR_NOIGNORE *
return convention and the PREPARE_ERROR / FAIL_* / SUCCEED_RETURN pattern.
Four conventions hold across the whole library, and a new wrapper that breaks one of them is wrong even if it compiles and passes:
- A NULL out-param is a caller error, not "don't care".
- Finding nothing is success -- searching functions write NULL or zero and return NULL.
- Truncation is a failure. Take the destination's size, raise AKERR_OUTOFBOUNDS, and write nothing rather than a prefix.
- Clear
errnobefore the wrapped call and read it back throughAKSL_ERRNO_OR, so no error can carry status 0 -- which every downstreamDETECTreads as success.
Every new public function needs a Doxygen block on its declaration with
@brief, a @param per parameter, a @throws per status it can raise, and
@return. The docs target fails otherwise.
The build is -Wall -Wextra and CI adds -Werror. -Wpedantic is deliberately
off: libakerror's FAIL_* macros trip "ISO C99 requires at least one argument
for the ..." on their own expansion, not on anything at the call site.
Testing Guidelines
Add a new test by creating tests/test_mything.c and adding mything to the
right list in CMakeLists.txt. AKSL_TESTS must exit zero.
AKSL_WILL_FAIL_TESTS are deliberate abort/contract tests.
AKSL_KNOWN_FAILING_TESTS assert documented defects from TODO.md; when one
starts unexpectedly passing, move it into AKSL_TESTS with the fix. Both of the
latter are currently empty -- all six confirmed defects are fixed -- but the
mechanism stays for the next one.
tests/negative/ holds sources that must fail to compile. Each is an
EXCLUDE_FROM_ALL target built with -Werror and registered as a WILL_FAIL
CTest entry, so the test passes only when the compile fails. They cover the two
guarantees the compiler enforces and nothing else does: AKERR_NOIGNORE and the
format attributes. Drop either in a refactor and every ordinary test still
passes.
tests/consumer/ is configured standalone against CMAKE_PREFIX_PATH, not as
part of this build, because being part of this build is what would let it pass
without testing anything. It covers the paths only an install has: the version
file, find_dependency(akerror) resolving, and the exported
akstdlib::akstdlib target. CI runs it after cmake --install.
Coverage is 99.5% of lines and 100% of functions across all four sources; CI
gates at 90 (line) / 40 (branch), so new code needs tests in the same commit. Run
cmake --build build-coverage --target coverage and check the uncovered-line
listing before proposing a change. Tests for behaviour that TODO.md records as
defective belong in AKSL_KNOWN_FAILING_TESTS asserting the correct contract —
do not pin current-but-wrong behaviour in AKSL_TESTS, since that turns the
eventual fix into a test failure.
Commit & Pull Request Guidelines
Recent commits use short imperative summaries, for example Add memory wrapper tests and Make error-status assertions authoritative. Keep commits focused
and include tests with behavior changes. Pull requests should describe the
changed API or behavior, list the CTest/sanitizer/mutation commands run, and
link the relevant TODO.md item or issue when fixing a known defect.
Agent-Specific Instructions
Do not modify generated build trees, profiling artifacts, or untracked scratch
files unless explicitly asked. Prefer small, test-backed changes and update
README.md or TODO.md when changing documented workflows or known failures.