I love programming in C, but I always need slices, optionals, and
result types (a.k.a. "errors as values").
The aven.h
header contains unintrusive definitions for these types.
It also defines a debugger friendly assert
macro and bounds checked slice
access macros.
The library has expanded to include:
- optionals, results, slices, lists, queues, and pools, :
aven.h
- arena allocation:
aven/arena.h
(inspired by this post) - command line argument parsing:
aven/arg.h
- a C build system:
aven/build.h
,aven/build/common.h
- portable file system interaction:
aven/fs.h
- a tiny SIMD linear algebra library:
aven/math.h
- portable file path string manipulation:
aven/path.h
- portable process execution and management:
aven/proc.h
- a random number generator interface:
aven/rng.h
- slice-based strings:
aven/str.h
- a bare-bones test framework:
aven/test.h
- portable thread pools:
aven/thread_pool.h
- portable high precision timing:
aven/time.h
- portable directory watching (Windows + Linux only):
aven/watch.h
Everything is cross-platform for Linux and Windows. Many things will work on non-Linux POSIX systems, but not all1.
All identifiers for functions, variables, and macros are snake case
and begin with a prefix for the corresponding header path, except for the
core defined in aven.h
. E.g. the alloc function defined in aven/arena.h
is
aven_arena_alloc
.
When built as a separate translation unit using the build system (see below),
the headers will only include the following C standard headers:
stddef.h
, stdbool.h
, stdint.h
, and stdassert.h
.
If compiling for C11 then stdalign.h
and stdnoreturn.h
are also included.
If using the standalone aven/time.h
portable timing header, then the libc
time.h
is included for timespec
support.
When used as a header only library via the AVEN_IMPLEMENTATION
macro,
a small number of other C standard library headers will be included.
For Windows targets a few C runtime headers are included (e.g. io.h
), but
bespoke declarations are used in lieu of the massive unmanageable Win32
headers2.
For Linux targets, some files require POSIX features to be enabled
( _POSIX_C_SOURCE >= 200112L
), and a few POSIX specific headers will be
included. Linux
specific features are used where necessary, e.g. sys/inotify.h
for directory
watching and /proc/self/exe
for exe path discovery; such functions simply
return errors on non-Linux POSIX targets.
The most prominent part of the library is the build system. I've long been frustrated by the poor portability of Makefiles and the complications of depending on a larger build system like CMake. I wanted a build system that satisfies the following requirements:
- it should depend only on the existence of a C compiler (
cc
), a linker (cc
or a separateld
), and (maybe) an archiver (ar
); - it should include a portable API to interact with the filesystem
(
mkdir
,rm
,rmdir
,touch
) wihtout relying on external binaries3; - builds should be acyclic directed graphs of steps;
- the user must be able to specify exactly what executables and flags will
be used for each build tool, e.g. how the variables
CC
andCFLAGS
are used in Makefiles; - there must by a standard easy way for a parent project to build and use artifacts from a dependency project.
The following toochains are fully supported, e.g. the default configuration
should work out-of-the-box when one is used to compile the build.c
.
- GNU (Linux + Windows w/MinGW): -cc
gcc
-arar
- clang (Linux + Windows w/MSVC or MinGW): -cc
clang
-arllvm-ar
- MSVC (Windows): -cc
cl.exe
-ldlink.ex
-arlib.exe
- tinycc (Linux + Windows): -cc
tcc
-ccflags "-D__BIGGEST_ALIGNMENT__=16" -artcc
-arflags "-ar -rcs"
The following toolchains are undectectable from predefined macros, but have been tested with the indicated configuration.
- Zig (Linux + Windows): -cc
zig
-ccflags "cc" -ldflags "cc" -arzig
-arflags "ar -rcs" - cproc w/GNU (Linux): -cc
cproc
-ccflags "-std=c11" -arar
Hopefully many other toolchains are supported as well! The MSVC toolchain is so weird that the build configuration has been expanded to be very accommodating.
A static object file can built using the contained build system.
On Linux systems you can compile the build system with make
.
make
You can also simply compile it manually with your favorite C compiler,
e.g. using gcc
gcc -o build build.c
or tinycc
tcc -D__BIGGEST_ALIGNMENT__=16 -o build build.c
or MSVC on Windows.
cl.exe /std:c11 /Fe:build.exe build.c
./build help
./build
./build test
./build clean
Since the build system is simple and flexible, cross compilation is achievable
regardless of the host or toolchain. The commands below build the build system on a
host x86_64
Linux machine with TinyCC, then create build artifacts for
an x86_64
Windows target using MinGW-w64.
tcc -D__BIGGEST_ALIGNMENT__=16 -o build build.c
./build -cc "x86_64-w64-mingww32-gcc" \
-ar "x86_64-w64-mingw32-ar" \
-windres "x86_64-w64-mingw32-windres" \
-ccflags "-std=c11 -O3 -Werror -Wall -Wextra" \
-exext ".exe" -soext ".dll" \
-ldwinflag "-mwindows" \
-syslibs "kernel32 user32 gdi32 shell32" \
-winutf8
Note that the libaven
repo
itself doesn't produce any executable build artifacts, try this command
in a project that produces a graphical application like libavengraph
.
Footnotes
-
Some things like file system notifications and detecting the path to a running executable are not standard across POSIX systems. Currently everything in
aven/watch.h
and theaven_path_exe
function are implemented for Windows and Linux only. ↩ -
I like to know what is in my C namespace. I am a musl man and can easliy read through the simple libc and Linux headers. Windows Win32 API on the other hand has massive "inaccessible" headers, but good documentation and little variation accross architecture. Thus is simply nicer to write the definitions myself. ↩
-
If you have ever tried to write a
make clean
step that works on both Linux and Windows, then you know my motivation here :( ↩