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.
Reducing GNU's conventions down to simple bullet points:
CC = gcc
) with any flags defined in a separate variable
(i.e.: CFLAGS = -g
).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:
CFLAGS
is configurable for augmenting the produced
artifact (i.e.: CFLAGS=-Oz
for optimizing for size,
ommitting debug symbols).-MMD -MP
produce *.d
files which are
generated Makefiles declaring additional dependencies of that object
file. -include $(DEPS)
ensures those dependencies are
respected when determining what needs to be rebuilt.I collect coverage via this snippet:
KCOV = kcov
KCOVFLAGS = --include-path=.
.PHONY: coverage
coverage: test
$(KCOV) $(KCOVFLAGS) coverage ./test
Currently, I only employ a single linter, include-what-you-use:
.PHONY: iwyu
iwyu: $(SRCS) $(wildcard *.h)
$^; do $(IWYU) $(IWYUFLAGS) "$$src"; done for src in
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
., @object
.type EMBED_NAMEalign 4
.:
EMBED_NAME incbin EMBED_SOURCE
.byte 0x00
.global EMBED_LEN
.:
EMBED_LEN int EMBED_LEN - EMBED_NAME - 1
.
#else-elf embeding
#error TODO implement non #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_$* \
$*_len -c embed.S -o $@ -DEMBED_LEN=embed_sql_
⚠️ 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.