Skip to content

Instantly share code, notes, and snippets.

@rlcamp
Last active February 1, 2024 20:38
Show Gist options
  • Save rlcamp/daa32007c6c9d501c2c2ecee5c28e384 to your computer and use it in GitHub Desktop.
Save rlcamp/daa32007c6c9d501c2c2ecee5c28e384 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# richard campbell
# isc license
#
# assumptions/caveats:
# - this script is run by bash 3.2+, produces a Makefile suitable for gnu make 3.81+
# - targets to build have a main function in a .c file of the same name
# - each module depended upon is in the same directory
# - each module depended upon consists of one .c and one .h file
# - if you need to link in external libraries, you still need to manually provide those to make via LDLIBS
# todo:
# - some things could probably be bash arrays rather than multiline strings
# - this thing crawls the entire tree twice, once to build the header dependencies and once to build the link-time dependencies. probably an easy way to do them both at once
set -euo pipefail
# if a Makefile already exists that was not autogenerated by this code, then just write to stdout
if ([ ! -f Makefile ] || grep '# autogenerated' Makefile >/dev/null) && [ -t 1 ]; then exec 1>Makefile ; fi
# this function could be implemented using cc -MM but it would still require all of the postprocessing
function get_headers_recursive() {
for module in $(sed -n 's/\#include \"\([A-Za-z0-9_]*\)\.h\"/\1/p' ${1}.[c,h] | grep -v '^'$1'$' | sort -u); do
printf "${module}\n"
get_headers_recursive $module
done
}
printf '# autogenerated\n'
printf '# if you ever see a segfault or other unexpected nondeterminism in this (or any other) code,\n'
printf '# the immediate muscle memory response should be to recompile with the following, and then\n'
printf '# rerun the failing use case:\n'
printf '# make clean && make CFLAGS="-Og -g -fno-inline -fsanitize=address,undefined"\n\n'
printf '# overrideable vars used in implicit make rules\n'
printf '# only default to -march=native if not on an arm mac\n'
printf 'ifeq (,$(findstring arm64,$(shell uname -m)))\n'
printf 'TARGET_ARCH ?= -march=native\n'
printf 'endif\n'
printf '\n'
printf 'CFLAGS ?= %s\n' "${CFLAGS:--Os}"
printf '# older versions of gcc need -fcx-limited-range, in others its effect is implied by -ffinite-math-only\n'
printf 'ifeq (0,$(shell cc -fcx-limited-range -x c -o /dev/null -c - < /dev/null 2>/dev/null; echo $$?))\n'
printf '\tCFLAGS += -fcx-limited-range\n'
printf 'endif\n'
printf '\n'
printf 'CPPFLAGS += -Wall -Wextra -Wshadow -Wmissing-prototypes\n'
printf 'LDFLAGS += ${CFLAGS}\n'
if printenv LDLIBS >/dev/null; then printf 'LDLIBS ?= %s\n' "$LDLIBS"; fi
printf '\n'
TARGETS=$(grep -l 'int main(' *.c | sed 's/\.c//g')
TARGETS_STRING=$(for target in ${TARGETS}; do printf "${target} " ; done | sed 's/ $//')
printf '# list of targets to build, generated from .c files containing a main() function:\n\n'
printf 'TARGETS=%s\n\n' "${TARGETS_STRING}"
printf 'all : ${TARGETS}\n\n'
ALL_POSSIBLE_HEADER_FILES=''
printf '# for each target, the list of objects to link, generated by recursively crawling include statements with a corresponding .c file:\n\n'
for target in ${TARGETS}; do
HEADER_FILES=$(get_headers_recursive $target | sort -u)
ALL_POSSIBLE_HEADER_FILES=$((printf "${ALL_POSSIBLE_HEADER_FILES}\n${target}\n${HEADER_FILES}") | sort -u)
printf "${target} : ${target}.o$(for object in $HEADER_FILES; do if [ -f ${object}.c ]; then printf ' %s.o' $object; fi; done)\n";
done
printf '\n'
printf '# for each object, the list of headers it depends on, generated by recursively crawling include statements:\n\n'
for object in ${ALL_POSSIBLE_HEADER_FILES}; do
if [ ! -f ${object}.c ]; then continue; fi
HEADER_FILES=$(get_headers_recursive $object | sort -u)
printf "${object}.o :$(for header in $HEADER_FILES ${object}; do if [ -f ${header}.h ]; then printf ' %s.h' $header; fi; done)\n"
done
printf '\n'
printf '*.o : Makefile\n'
printf '\n'
printf 'clean :\n\t$(RM) -rf *.o *.dSYM ${TARGETS}\n'
printf '.PHONY: clean all\n'
# autogenerated
# if you ever see a segfault or other unexpected nondeterminism in this (or any other) code,
# the immediate muscle memory response should be to recompile with the following, and then
# rerun the failing use case:
# make clean && make CFLAGS="-Og -g -fno-inline -fsanitize=address,undefined"
# overrideable vars used in implicit make rules
# only default to -march=native if not on an arm mac
ifeq (,$(findstring arm64,$(shell uname -m)))
TARGET_ARCH ?= -march=native
endif
CFLAGS ?= -Os
# older versions of gcc need -fcx-limited-range, in others its effect is implied by -ffinite-math-only
ifeq (0,$(shell cc -fcx-limited-range -x c -o /dev/null -c - < /dev/null 2>/dev/null; echo $$?))
CFLAGS += -fcx-limited-range
endif
CPPFLAGS += -Wall -Wextra -Wshadow -Wmissing-prototypes
LDFLAGS += ${CFLAGS}
# list of targets to build, generated from .c files containing a main() function:
TARGETS=qrpn
all : ${TARGETS}
# for each target, the list of objects to link, generated by recursively crawling include statements with a corresponding .c file:
qrpn : qrpn.o
# for each object, the list of headers it depends on, generated by recursively crawling include statements:
qrpn.o : qrpn.h
*.o : Makefile
clean :
$(RM) -rf *.o *.dSYM ${TARGETS}
.PHONY: clean all
@rlcamp
Copy link
Author

rlcamp commented Apr 24, 2021

note that this scales as O(N^🤭) in the number of files, there are probably much better solutions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment