From 00cceaba62bd5eee22361461011a732b41c4bf99 Mon Sep 17 00:00:00 2001 From: Laurent MAZET Date: Fri, 26 Sep 2025 18:25:57 +0200 Subject: [PATCH] generic makefile --- makefile | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 makefile diff --git a/makefile b/makefile new file mode 100644 index 0000000..9878443 --- /dev/null +++ b/makefile @@ -0,0 +1,288 @@ + +# Default flags + +AR ?= ar +CC ?= gcc +RANLIB ?= ranlib + +#INCLUDES += -I../debug -D__MEMORY_ALLOCATION__ +OFLAGS = -O4 -Os +#OFLAGS = -O0 +#OFLAGS = -O4 -ffast-math -finline-functions +#OFLAGS = -O4 -finline-functions +#OFLAGS += -mtune=pentium3 -mmmx -msse -msse2 -m3dnow +#OFLAGS += -minline-all-stringops -fsingle-precision-constant +#OFLAGS += -malign-double +CFLAGS += -W -Wall -Wextra -g +#CFLAGS += -std=c99 -D_XOPEN_SOURCE=500 +#CFLAGS += -D_XOPEN_SOURCE=500 +CFLAGS += $(OFLAGS) $(INCLUDES) $(OPTIONS) +LDFLAGS += -g $(LDOPTS) $(OPTIONS) + +LDOPT = linker +AROPT = archive +MV = mv +ifneq (, $(findstring linux, $(MAKE_HOST))) +# Linux +else ifneq (, $(findstring mingw, $(MAKE_HOST))) +# Windows MinGw +CFLAGS += -DWIN32 +#LDLIBS += -lws2_32 +LDOPT = winlnk +AROPT = +else ifneq (, $(findstring cygwin, $(MAKE_HOST))) +# Windows CygWin +CFLAGS += -DWIN32 +LDOPT = winlnk +AROPT = +else ifneq (, $(findstring msdos, $(MAKE_HOST))) +# MSDOS +LDOPT = doslnk +AROPT = +MV = move +endif + +ifeq ($(DESTDIR),) +DESTDIR = /usr/local +endif + +BINDIR = $(DESTDIR)/bin +INCDIR = $(DESTDIR)/include +LIBDIR = $(DESTDIR)/lib + +# Targets + +ALLEXE = $(shell for f in *.c; do grep -q '/\*\s*$(LDOPT):' $$f && echo $${f/.c}; done) + +ALLDLL = $(shell for f in *.c; do grep -q '/\*\s*$(AROPT):' $$f && echo lib$${f/.c}; done) + +SHELL = bash + +#MAKE = mingw32-make +MAKEFLAGS += -s +include $(wildcard .makefile) + +# Functions + +TITLE = echo -en "\033[0;1m$(strip $(1))\033[0;0m:\t" +PASS = echo -e "\033[1;32m$(strip $(1))\033[0;0m" +WARN = echo -e "\033[1;33m$(strip $(1))\033[0;0m" +FAIL = echo -e "\033[1;31m$(strip $(1))\033[0;0m" + +INSTALL = test -d $(2) || mkdir -p $(2) && cp -pa $(1) $(2) && chmod a+rX,go-w $(2) $(addprefix $(2)/,$(1)) + +VALID = $(call TITLE, $(1)) && $(2) && $(call PASS, SUCCESS) || { $(call FAIL, FAILED); test; } + +GETCOMMENTS = awk '/\/\*[ \t]*$(1):/,/\*\// { sub(/.*\/\*[ \t]*$(1):/, ""); sub (/[ \t]*\*\/.*/, ""); print } /\/\/[ \t]*$(1):/ {sub (/.*\/\/[ \t]*$(1):/, ""); print }' $(2) +#GETCOMMENTS = perl -- getcomments.pl -p='$(1):\s' -f='%' $(2) + +DATE = $(shell git log -1 HEAD --format=%ai|awk '{print $$1}') +#DATE = $(shell hg log -r-1 --template '{date|isodate}'|awk '{print $$1}') + +## Generic rules + +all: depends + [ -z "$(ALLEXE)" ] || $(MAKE) exe + [ -z "$(ALLDLL)" ] || $(MAKE) lib + +analyze: + make purge + scan-build make + #scan-build -stats make + +count: + wc $(MAKEFILE_LIST) $(sort $(wildcard \ + $(patsubst %.o,%.c,$(shell $(call GETCOMMENTS,$(LDOPT),$(ALLEXE:%=%.c) $(MAKEFILE_LIST)))) \ + $(patsubst %.o,%.h,$(shell $(call GETCOMMENTS,$(LDOPT),$(ALLEXE:%=%.c) $(MAKEFILE_LIST)))) \ + $(patsubst %.o,%.c,$(shell $(call GETCOMMENTS,$(AROPT),$(ALLDLL:lib%=%.c) $(MAKEFILE_LIST)))) \ + $(patsubst %.o,%.h,$(shell $(call GETCOMMENTS,$(AROPT),$(ALLDLL:lib%=%.c) $(MAKEFILE_LIST)))))) + +clean: + $(call TITLE, "Cleaning") + touch clean + rm -f clean $(wildcard \ + $(shell $(call GETCOMMENTS,$(LDOPT),$(ALLEXE:%=%.c) $(MAKEFILE_LIST))) \ + $(patsubst %.o,%.d,$(shell $(call GETCOMMENTS,$(LDOPT),$(ALLEXE:%=%.c) $(MAKEFILE_LIST)))) \ + $(shell $(call GETCOMMENTS,$(AROPT),$(ALLDLL:lib%=%.c) $(MAKEFILE_LIST))) \ + $(patsubst %.o,%.d,$(shell $(call GETCOMMENTS,$(AROPT),$(ALLDLL:lib%=%.c) $(MAKEFILE_LIST)))) \ + *.d *.ld *.lld *.log *.o *.test .exec_* gmon.out) + $(call PASS, SUCCESS) + +depends: + [ "$(ALLEXE)" ] || $(call WARN,no exec to proceed) + for exe in $(ALLEXE); do INCLUDES="`$(call GETCOMMENTS,includes,$$exe.c)`" make `$(call GETCOMMENTS,$(LDOPT),$$exe.c) | sed 's/[^ ]*\.[^o]//g' | sed 's/\.o/.d/g'` $$exe.d || exit 1; done + [ -z "$(ALLEXE)" ] || make $(patsubst %, %.ld, $(ALLEXE)) + [ "$(ALLDLL)" ] || $(call WARN,no library to proceed) + for dll in $(ALLDLL:lib%=%); do INCLUDES="`$(call GETCOMMENTS,includes,$$dll.c)`" make `$(call GETCOMMENTS,$(AROPT),$$dll.c) | sed 's/[^ ]*\.[^o]//g' | sed 's/\.o/.d/g'` $$dll.d || exit 1; done + [ -z "$(ALLDLL)" ] || make $(patsubst %, %.lld, $(ALLDLL)) + +documentation: doxygen.conf $(shell test -f doxygen.conf && awk '{sub(/#.*/,"")} /^FILE_PATTERNS/,!/\\$$/ {sub(/\\$$/,"");sub(/.*=/,"");print}' doxygen.conf) + doxygen $<; true + sed -i s/__BEGIN_DECLS//g documentation/html/*.html + sed -i s/\\_\\-\\_BEGIN\\_\\-DECLS//g documentation/latex/*.tex + +exe: depends + [ "$(ALLEXE)" ] || { $(call FAIL,no exec to proceed); test; } && for exe in $(ALLEXE); do INCLUDES="`$(call GETCOMMENTS,includes,$$exe.c)`" CFLAGS="`$(call GETCOMMENTS,cflags,$$exe.c)`" $(MAKE) $$exe.exe || exit 1; done + +gcovs: wipe + $(MAKE) $(addprefix gcov_,$(ALLEXE)) + +gprofs: + $(MAKE) $(addprefix gprof_,$(ALLEXE)) + +lib: depends + [ "$(ALLDLL)" ] || { $(call FAIL,no exec to proceed); test; } && for dll in $(ALLDLL:lib%=%); do INCLUDES="`$(call GETCOMMENTS,includes,$$dll.c)`" CFLAGS="`$(call GETCOMMENTS,cflags,$$exe.c)`" $(MAKE) lib$$dll.a || exit 1; done + +purge: clean + $(call TITLE, "Purging") + touch purge + rm -f purge $(ALLEXE:%=%.exe) $(ALLDLL:%=%.a) + $(call PASS, SUCCESS) + +valgrinds: + $(MAKE) all + $(MAKE) $(addprefix valgrind_,$(ALLEXE)) + +wipe: purge + $(call TITLE, "Wiping") + touch wipe + rm -f wipe $(wildcard *.gcda *.gcno *.gcov *.glog) + $(call PASS, SUCCESS) + +tests: + -rm -f $(ALLEXE:%=%.exe) + $(MAKE) all + $(MAKE) $(addprefix test_,$(ALLEXE)) + +archive: wipe changelog + [ \! -f doxygen.conf ] || make documentation + name=`basename "$$(pwd)"`; cd ..; tar cvz --exclude='.git*' -f $$name-$(DATE).tgz $$name + +## Main rules + +include $(sort $(wildcard \ + $(patsubst %.o,%.d,$(shell $(call GETCOMMENTS,$(LDOPT),$(ALLEXE:%=%.c) $(MAKEFILE_LIST))))) \ + $(wildcard $(patsubst %.o,%.d,$(shell $(call GETCOMMENTS,$(AROPT),$(ALLDLL:lib%=%.c) $(MAKEFILE_LIST)))))) +include $(wildcard *.ld) +include $(wildcard *.lld) + +gcov_%: + $(MAKE) purge + $(MAKE) depends + OPTIONS="-coverage -O0" $(MAKE) ${@:gcov_%=%}.exe + $(MAKE) test_$(@:gcov_%=%) + gcov `sed -e 's/\.exe:/.c/;s/\.o/.c/g;s/ -l[^ ]*//g' $(@:gcov_%=%.ld)` + $(MAKE) purge + grep '^ *#####' *.c.gcov || true + +gprof_%: + $(MAKE) purge + $(MAKE) depends + OPTIONS="-pg" $(MAKE) ${@:gprof_%=%}.exe + $(MAKE) ${@:gprof_%=%}.test + IFS=$$'\n'; id=1; \ + for test in `cat ${@:gprof_%=%}.test | sed 's,${@:gprof_%=%}.exe,./${@:gprof_%=%}.exe,g'`; do \ + log=${@:gprof_%=%}.prof-$$id.glog; \ + $(call TITLE, test: $$test); \ + echo $$test > $$log; \ + eval $$test >> $$log; \ + [ $$? -eq 0 ] \ + && echo -e "\033[1;32mSUCCESS\033[0;0m" \ + || echo -e "\033[1;31mFAILED\033[0;0m"; \ + [ -f gmon.out ] && { gprof ${@:gprof_%=%}.exe gmon.out >> $$log; rm gmon.out; }; \ + let id++; \ + done; + $(MAKE) purge + +%.test: %.c + $(call TITLE, "Building $@") + $(call GETCOMMENTS,test, $<) > $@ + $(call PASS, SUCCESS) + -rm -f _ + +test_%: %.test %.exe + IFS=$$'\n'; RC=0; \ + for test in `cat $< | sed 's,${<:.test=.exe},$(VALGRIND) ./${<:.test=.exe},g'`; do \ + echo "=== $$test ==="; \ + eval $$test; \ + [ $$? -eq 0 ] && echo -e "\033[1;32mSUCCESS\033[0;0m" \ + || { echo -e "\033[1;31mFAILED\033[0;0m"; RC=1; }; \ + test "$$RC" = 1 -a "$(STOP)" = 1 && break; \ + done; \ + test "$$RC" -ne 1 + +valgrind_%: %.exe + VALGRIND="valgrind -v --leak-check=full --log-fd=3"; \ + export VALGRIND; \ + $(MAKE) $(@:valgrind_%=test_%) 3>$@.log + +%.d: %.c + $(call TITLE, "Building $@") + $(CC) $(INCLUDES) -MM $< -o $@~ + echo ${<:.c=.o}: $(shell $(call GETCOMMENTS,depend, $<)) >> $@~ + $(MV) $@~ $@ + $(call PASS, SUCCESS) + +lib%.lld: %.c + $(call TITLE, "Building $@") + echo ${@:.lld=.a}: $(shell $(call GETCOMMENTS,$(AROPT), $<) | awk '{for (i=1;i<=NF;i++) if ($$(i) ~ /.o$$/) printf " %s", $$(i)}') > $@ + $(call PASS, SUCCESS) + +%.ld: %.c + $(call TITLE, "Building $@") + echo ${<:.c=.exe}: $(shell $(call GETCOMMENTS,$(LDOPT), $<) | awk '{for (i=1;i<=NF;i++) if ($$(i) ~ /.o$$/) printf " %s", $$(i)}') > $@ + $(call PASS, SUCCESS) + +%.o: %.c + $(call TITLE, "Building $@") + $(CC) $(CFLAGS) $(INCLUDES) $(shell $(call GETCOMMENTS,cflags, $<)) -c $< -o $@ + $(call PASS, SUCCESS) + + +%.exe: %.c + INCLUDES="$(shell $(call GETCOMMENTS,includes, $<))" CFLAGS="$(shell $(call GETCOMMENTS,cflags, $<))" make $(<:.c=.d) + INCLUDES="$(shell $(call GETCOMMENTS,includes, $<))" CFLAGS="$(shell $(call GETCOMMENTS,cflags, $<))" make $(<:.c=.o) + $(call TITLE, "Building $@") + $(CC) $(LDFLAGS) ${<:.c=.o} $(shell $(call GETCOMMENTS,$(LDOPT), $<)) $(LDLIBS) -o $@ + $(call PASS, SUCCESS) + +lib%.a: %.c + INCLUDES="$(shell $(call GETCOMMENTS,includes, $<))" CFLAGS="$(shell $(call GETCOMMENTS,cflags, $<))" make $(<:.c=.d) + INCLUDES="$(shell $(call GETCOMMENTS,includes, $<))" CFLAGS="$(shell $(call GETCOMMENTS,cflags, $<))" make $(<:.c=.o) + $(call TITLE, "Building $@") + $(AR) rc $@ ${<:.c=.o} $(shell $(call GETCOMMENTS,$(AROPT), $<)) + $(RANLIB) $@ + $(call PASS, SUCCESS) + +## install rules + +install_lib%: lib%.a + $(call TITLE, "Install $<") + $(call INSTALL, $(shell $(call GETCOMMENTS,include, `echo $< | sed 's/^lib//;s/\.a$$/.c/'`)), $(INCDIR)/`echo $< | sed 's/^lib//;s/\.a$$//'`) + sed -i 's/\(#include\) "\(.*\)"/\1 <'`echo $< | sed 's/^lib//;s/\.a$$//'`'\/\2>/' $(INCDIR)/`echo $< | sed 's/^lib//;s/\.a$$//'`/*.h + $(call INSTALL, $<, $(LIBDIR)) + $(call PASS, SUCCESS) + +install_%: %.exe + $(call TITLE, "Install $<") + $(call INSTALL, $<, $(BINDIR)) + $(call PASS, SUCCESS) + +install_lib: + make $(addprefix install_,$(ALLDLL)) + +install_exe: + make $(addprefix install_,$(ALLEXE)) + +changelog: + echo "Logs from $(DATE)" > $@.txt + git log --pretty=format:'- %s' >> $@.txt +# hg log --template changelog >>$@.txt + +## Phony + +.PHONY: all analyze archive changelog clean count depends documentation exe gcovs gprofs install_lib install_exe lib purge tests valgrinds wipe + +## Precious + +.PRECIOUS: %.d %.o -- 2.30.2