generic makefile
authorLaurent MAZET <laurent.mazet@thalesgroup.com>
Fri, 26 Sep 2025 16:25:57 +0000 (18:25 +0200)
committerLaurent MAZET <laurent.mazet@thalesgroup.com>
Fri, 26 Sep 2025 16:25:57 +0000 (18:25 +0200)
makefile [new file with mode: 0644]

diff --git a/makefile b/makefile
new file mode 100644 (file)
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