C Build System

2023-10-29

There is no single C build system; however, the one I prefer at present are raw Makefiles. These are simple enough to understand for a simple project as long as they conform to some general guidelines. I try to follow the GNU Makefile Conventions as closely as seems reasonable. The following is what I've settled upon.

Overview

Reducing GNU's conventions down to simple bullet points:

Object Files

I compile all object files via this snippet:

CFLAGS = -g
ALL_CFLAGS = -I. -Wall -std=c99 -MMD -MP $(CFLAGS)

%.o: %.c
    $(CC) $(ALL_CFLAGS) -c $< -o $@

-include $(DEPS)

The salient points are:

Coverage

I collect coverage via this snippet:

KCOV = kcov
KCOVFLAGS = --include-path=.

.PHONY: coverage
coverage: test
    $(KCOV) $(KCOVFLAGS) coverage ./test

Linters

Currently, I only employ a single linter, include-what-you-use:

.PHONY: iwyu
iwyu: $(SRCS) $(wildcard *.h)
    for src in $^; do $(IWYU) $(IWYUFLAGS) "$$src"; done

Data File Embedding

This is the most complex of build steps I've employed so far. It embeds data files as symbols into a read-only section of an object file. There's a few guide out there which describe this. I followed Embedding of binary data into programs in particular but augmented it to fit my sensibilities.

In particular, I wrote an assembly file which is given particular preprocessor defines by the Makefile which represent the data file path and symbols to use. This seems the best option since the assembly file can use preprocessor logic to correct for toolchain differences. I've only defined assembly which works for ELF targets under a GNU-like assembler:

#ifdef __ELF__
.section .rodata, "", @progbits
.global EMBED_NAME
.type EMBED_NAME, @object
.align 4
EMBED_NAME :
.incbin EMBED_SOURCE
.byte 0x00
.global EMBED_LEN
EMBED_LEN :
.int EMBED_LEN - EMBED_NAME - 1
#else
#error TODO implement non-elf embeding
#endif

embed.S is compiled from the Makefile with the correct preprocessor defines. The following is an example for embedding all SQL files in the Makefile's directory:

%.sql.o: %.sql embed.S
    $(CC) -D'EMBED_SOURCE="$<"' -DEMBED_NAME=embed_sql_$* \
        -DEMBED_LEN=embed_sql_$*_len -c embed.S -o $@

⚠️ This does not properly handle escaping file names. Make certain any embed file names are permissible within a string for any assembler used.

ℹ️ This causes a warning in some versions of GCC regarding executable stacks. As far as I can tell, this is innocuous and deprecated by future versions of GCC.