#---*- Makefile -*-------------------------------------------------------------
#$Author: antanas $
#$Revision: 9580 $
#$Date: 2023-05-15 19:28:46 +0300 (Mon, 15 May 2023) $
#$URL: svn+ssh://www.crystallography.net/home/coder/svn-repositories/cod-tools/tags/v3.8.0/src/components/codcif/Makefile $
#------------------------------------------------------------------------------

MAKECONF_FILES = ${filter-out %~, ${wildcard Makeconf*}}

ifneq ("${MAKECONF_FILES}","")
include ${MAKECONF_FILES}
endif

#
# ${SRC_DIR} contains C sources to be compiled into the
# local library ${LOCAL_LIB}
#

ifeq ("${SRC_DIR}","")
SRC_DIR = .
endif

#
# ${OBJDIR} is where the compiled object files should be stored
#

OBJDIR = ./obj

#
# GEN_DIR will contain any .c and .h files that are automatically
# generated by scripts or makefiles
#

GEN_DIR = ./generated

#
# Each .c file in the ./programs/ directory is considered to be a source
# for some main program (i.e. contains main() function).  It will be
# compiled and linked with all .c files in directories specified by
# the OTHER_DIRS variable. OTHER_DIRS should be defined in the
# Makeconf file in each directory.
#

PROGDIR    = ${SRC_DIR}/programs
PRG_FILES  = ${wildcard ${PROGDIR}/*.c}
EXE_FILES  = ${patsubst %.c, %, ${notdir ${PRG_FILES}}}
PRG_SOURCE = ${notdir ${PRG_FILES}}
PRG_DIRS   = ${dir ${PRG_FILES}}
PRG_DEPEND = ${join ${PRG_DIRS}, ${PRG_SOURCE:%.c=.%.d}}

# The file that holds version number of the package:

VFILE = .version

# The '^\#' is used instead of '^#' to circumvent a bug in older versions of
# Make (e.g GNU Make 4.2.1). However, the '^\#' sequence also causes a warning
# about a stray backslash in newer versions of grep (e.g. GNU grep 3.8) unless
# the -P option is used. Alternatively, the following Perl program may be used:
## VERSION := $(shell perl -n -e 'print unless /^\#/;' $(VFILE))
VERSION := $(shell grep -v -P '^\#' $(VFILE))
SO_MAJOR := $(shell echo ${VERSION} | cut -d . -f 1)
SO_MINOR := $(shell echo ${VERSION} | cut -d . -f 2-)

# The header file that holds version number of the package:

VERSION_H = version.h

#
# The first program (alphabetically) in the ./programs directory is a
# main executable (target). It will be run for every file in ./inputs
# when 'make test' is invoked.
#

TARGET = ${firstword ${EXE_FILES}}

#
# All .c files in the current directory will be compiled and the object
# files put into the local library; the library in the directory
# 'dir' will be called 'libdir.a'
#

LOCAL_CFILES := ${notdir ${wildcard ${SRC_DIR}/*.c}}
LOCAL_CFILES := ${filter-out %.tab.c lex.yy.c %.lex.c, ${LOCAL_CFILES}}
LOCAL_OBJS    = ${LOCAL_CFILES:%.c=${OBJDIR}/%.o} ${wildcard %.lex.o %.tab.o}
LOCAL_DEPENDS = ${LOCAL_CFILES:%.c=.%.d}

PACKAGE := ${notdir ${shell pwd}}
LIBNAME := ${addprefix lib, ${PACKAGE}}

LOCAL_LIB := ${addprefix lib/, ${addsuffix .a, ${LIBNAME} }}
LOCAL_SO  := ${addprefix lib/, ${addsuffix .so.${VERSION}, ${LIBNAME} }}

#
# ./testprogs directory contains test drivers, each of them containing
# main() program that must be linked with all .o files from all .c
# sources from ${OTHER_DIRS}.
#
# Each test driver will then be run without arguments, and its output
# will be compared with the sample output in ./testout directory
#

TST_PRG_DIR  = ./testprogs
TST_EXE_DIR  = ./testbin

TST_SOURCES   = ${wildcard ${TST_PRG_DIR}/*.c}
TST_DRIVERS   = ${patsubst %.c, ${TST_EXE_DIR}/%, ${notdir ${TST_SOURCES}}}
TST_BASENAMES = ${notdir ${TST_SOURCES}}
TST_DIRS      = ${dir ${TST_SOURCES}}
TST_DEPEND    = ${join ${TST_DIRS}, ${TST_BASENAMES:%.c=.%.d}}

#
# All other .c files will be compiled to objects in the current
# directory, and used for linking
#

vpath %.c ${SRC_DIR} ${OTHER_DIRS}
vpath %.h ${SRC_DIR} ${OTHER_DIRS}

VPATH = ${OTHER_DIRS}

OTHER_CFILES := ${notdir ${wildcard ${addsuffix /*.c, ${OTHER_DIRS}}}}
OTHER_CFILES := ${filter-out %.tab.c lex.yy.c %.lex.c, ${OTHER_CFILES}}
OTHER_OBJS    = ${OTHER_CFILES:%.c=${OBJDIR}/%.o}
OTHER_DEPENDS = ${OTHER_CFILES:%.c=.%.d}

DEPEND_FILES = ${LOCAL_DEPENDS} ${OTHER_DEPENDS} ${PRG_DEPEND} ${TST_DEPEND}
DEL_OBJS     = ${LOCAL_OBJS} ${OTHER_OBJS}

#------------------------------------------------------------------------------
#
# Executables:
#

CC    = gcc
CCVER = ${CC} --version
GCC   = gcc
YACC  = bison -y

INCLUDES = ${sort -I. ${addprefix -I, ${SRC_DIR} ${OTHER_DIRS} ${LIB_DIRS}}}

## LDFLAGS  = ${addprefix -L, ${sort ${LIB_DIRS}}}
## LIBFLAGS = ${addprefix -l, ${LIB_NAMES}}

## LDFLAGS  = -L/usr/local/lib
LIBFLAGS = -lm

SVN_VERSION := $(shell svnversion)

ifeq (${CC},gcc)
    CFLAGS = -Wall -Wsign-compare -g ${OPTFLAGS} -DYYDEBUG=1 -D_YACC_ ${INCLUDES} -fPIC \
	-DSVN_VERSION="\"${SVN_VERSION}\"" \
	${EXTRA_CFLAGS}
else
    CFLAGS = ${OPTFLAGS} -DYYDEBUG=1 -D_YACC_ ${INCLUDES} \
	-DSVN_VERSION="\"${SVN_VERSION}\"" \
	${EXTRA_CFLAGS}
endif

#------------------------------------------------------------------------------

YYFILES = ${wildcard *.y}
YYOBJ   = ${YYFILES:%.y=${OBJDIR}/%.tab.o}

LINK_OBJ_FILES = ${YYOBJ} ${OTHER_OBJS}

#------------------------------------------------------------------------------

.PRECIOUS: %.o %.c %.lex.c %.tab.c %.a

.PHONY: all lib libs clean distclean cleanAll FORCE

all: ${LOCAL_LIB} ${LOCAL_SO} ${EXE_FILES} ${TST_DRIVERS}

lib libs: ${LIB_DIRS}

${LIB_DIRS}: FORCE
	${MAKE} -C $@

FORCE:;

#------------------------------------------------------------------------------

# ${LIB_DIRS} variable contains a list of directories, each of them
# containing a library that must be linked with programs in the
# current directory. The library in the directory 'dir' must be called
# 'dir/libdir.a'. We assume that the 'dir' directory contains a Makefile
# to build a corresponding library, and simply do 'make -C dir libdir.a'
# if the library does not exist. If the library exists, we will use
# whatever it contains, without checking its dependencies. To build the
# up-to-date libraries in ${LB_DIRS} directories use 'make libs' command.
# We omit the check of library dependencies just for speed; if the library
# is actively changed and must be constantly recompiled, one should use
# ${OTHER_DIRS} variable to include the *.c files of that directory
# into the compilation list.

LIB_NAMES := ${notdir ${LIB_DIRS}}
LIB_FILES := ${addprefix /lib/lib, ${addsuffix .a, ${LIB_NAMES} }}
LIB_FILES := ${join ${LIB_DIRS}, ${LIB_FILES} }

%.a:
	${MAKE} -C $(dir $(patsubst %/,%,$(dir $@))) \
		$(notdir $(patsubst %/,%,$(dir $@)))/$(notdir $@)

#------------------------------------------------------------------------------
#
# How to build library in the current directory
#

${LOCAL_LIB}: ${LOCAL_OBJS} ${YYOBJ}
	ar cr ${LOCAL_LIB} $^

${LOCAL_SO}: ${LOCAL_OBJS} ${YYOBJ}
	${CC} -shared -Xlinker -soname=${LIBNAME}.so.${SO_MAJOR} -o $@ $^ -lm

#------------------------------------------------------------------------------

MAKELOCAL_FILES = ${filter-out %~, ${wildcard Makelocal*}}

ifneq ("${MAKELOCAL_FILES}","")
include ${MAKELOCAL_FILES}
endif

include ${DEPEND_FILES}

.%.d: %.c
	${GCC} ${CFLAGS} -M -MG \
	$< | sed -e 's,^${notdir $*}.o:,${OBJDIR}/$*.o:,' > $@

#------------------------------------------------------------------------------

${OBJDIR}/%.o: %.c
	${CC} ${CFLAGS} -c $< -o $@

%: ${PROGDIR}/%.c ${LINK_OBJ_FILES} ${LOCAL_LIB} ${LIB_FILES} ${VERSION_H}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LIBFLAGS}

${TST_EXE_DIR}/%: ${TST_PRG_DIR}/%.c ${LINK_OBJ_FILES} \
                  ${LOCAL_LIB} ${LIB_FILES}
	${CC} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LIBFLAGS}

%.tab.c %.tab.h: %.y 
	${YACC} -v -d -p ${*:%_grammar=%} --file-prefix $* $<

#------------------------------------------------------------------------------

# When 'make test' is invoked, a TARGET executable will be run for
# every file in inputs that has extension ${EXT}. The outputs will be
# compared with the sample outputs files in ./outputs/*.out, and any
# differences will be recorded in ./outputs/*.diff #

TEST_DIR = ./tests
OUTPUT_DIR = ./outputs

TEST_FILES = ${wildcard ${TEST_DIR}/*${EXT}}
RES_FILES  = ${patsubst ${TEST_DIR}/%${EXT},${OUTPUT_DIR}/%.out,${TEST_FILES}}
DIFF_FILES = ${patsubst ${TEST_DIR}/%${EXT},${OUTPUT_DIR}/%.diff,${TEST_FILES}}

#
# Outputs and diffs from the standalone test drivers
#

## TST_OUT_DIR = ./testout

TST_OUTPUTS = ${addprefix ${TST_OUT_DIR}/, ${TST_BASENAMES:%.c=%.out}}
TST_DIFFS   = ${addprefix ${TST_OUT_DIR}/, ${TST_BASENAMES:%.c=%.diff}}

#
# Outputs and tests from the shell-driven tests
#

SHELL_TSTDIR = ./tests
SHELL_OUTDIR = ./outputs

SHELL_TESTS   = ${wildcard ${SHELL_TSTDIR}/*.sh}
SHELL_BASES   = ${notdir ${SHELL_TESTS}}
SHELL_OUTPUTS = ${addprefix ${SHELL_OUTDIR}/, ${SHELL_BASES:%.sh=%.out}}
SHELL_DIFFS   = ${addprefix ${SHELL_OUTDIR}/, ${SHELL_BASES:%.sh=%.diff}}

#------------------------------------------------------------------------------

.PHONY: out outputs test tests alltests check

out outputs: ${RES_FILES} ${TST_OUTPUTS} ${SHELL_OUTPUTS}

# Sorting the diff files to achieve proper test ordering
ALL_DIFFS = ${sort ${DIFF_FILES} ${TST_DIFFS} ${SHELL_DIFFS}}
test tests alltests check: ${ALL_DIFFS}

${RES_FILES}: ${EXE_FILES}
${DIFF_FILES}: ${EXE_FILES}

${OUTPUT_DIR}/%.diff: ${TEST_DIR}/%${EXT} ${TEST_DIR}/%${OPT_EXT}
	-@printf "%-30s " "$*:" ; \
	./$(shell echo $* | sed -e 's/_[0-9]*$$//') ${TEST_OPTIONS} \
		${TEST_OPTIONS} \
		$(shell grep -v '^#' ${word 2, $^}) \
		$< 2>&1 \
	| diff ${OUTPUT_DIR}/$*.out - > $@ ; \
	if [ $$? = 0 ]; then echo "OK"; else echo "FAILED:"; cat $@; fi

${OUTPUT_DIR}/%.out: ${TEST_DIR}/%${EXT} ${TEST_DIR}/%${OPT_EXT}
	-@test -f $@ || echo "$@:"
	-@test -f $@ || \
	./$(shell echo $* | sed -e 's/_[0-9]*$$//') \
		${TEST_OPTIONS} \
		$(shell grep -v '^#' ${word 2, $^}) \
		$< 2>&1 \
	| tee $@
	-@touch $@

${OUTPUT_DIR}/%.diff: ${TEST_DIR}/%${EXT}
	-@printf "%-30s " "$*:" ; \
	./$(shell echo $* | sed -e 's/_[0-9]*$$//') \
		${TEST_OPTIONS} \
		$<  2>&1 \
	| diff ${OUTPUT_DIR}/$*.out - > $@ ; \
	if [ $$? = 0 ]; then echo "OK"; else echo "FAILED:"; cat $@; fi

${OUTPUT_DIR}/%.out: ${TEST_DIR}/%${EXT}
	-@test -f $@ || echo "$@:"
	-@test -f $@ || \
	./$(shell echo $* | sed -e 's/_[0-9]*$$//') \
		${TEST_OPTIONS} \
		$< 2>&1 \
	| tee $@
	-@touch $@

${TST_OUT_DIR}/%.out: ${TST_EXE_DIR}/%
	-@test -f $@ || echo "$@:"
	-@test -f $@ || $< 2>&1 | tee $@
	-@touch $@

${TST_OUT_DIR}/%.diff: ${TST_EXE_DIR}/%
	-@printf "%-30s " "$*:" ; \
	./$< 2>&1 | diff ${TST_OUT_DIR}/$*.out - > $@ ; \
	if [ $$? = 0 ]; then echo "OK"; else echo "FAILED:"; cat $@; fi

.PHONY: shoutputs shout shtests shtest

${SHELL_DIFFS}: ${EXE_FILES}

shoutputs shout: ${SHELL_OUTPUTS}

shtest shtests: ${SHELL_DIFFS}

${SHELL_OUTDIR}/%.out: ${SHELL_TSTDIR}/%.sh ${TARGET}
	-@test -f $@ || echo "$@:"
	-@test -f $@ || $< ./${TARGET} 2>&1 | tee $@
	-@touch $@

${SHELL_OUTDIR}/%.diff: ${SHELL_TSTDIR}/%.sh ${TARGET}
	-@printf "%-30s " "$*:" ; \
	$< ./${TARGET} 2>&1 | diff ${SHELL_OUTDIR}/$*.out - > $@ ; \
	if [ $$? = 0 ]; then echo "OK"; else echo "FAILED:"; cat $@; fi

.PHONY: listdiff

listdiff: # test
	@find ${OUTPUT_DIR} ${TST_OUT_DIR} ${SHELL_OUTDIR} -name '*.diff' -size +0 \
	| sort -u | xargs --no-run-if-empty ls -l

#------------------------------------------------------------------------------

.PHONY: benchmark benchmarks bench

BENCH_DIR    = ./benchmarks
ARCH_TYPE    = ${shell uname -m}
BENCH_FILE   = ${BENCH_DIR}/times-${ARCH_TYPE}.dat
SIZE_FILE    = ${BENCH_DIR}/sizes-${ARCH_TYPE}.dat
BENCH_INPUTS = ${wildcard ${BENCH_DIR}/*${EXT}}

benchmarks benchmark bench: ${BENCH_FILE} ${SIZE_FILE}

${BENCH_FILE}: ${TARGET} ${BENCH_INPUTS}
	date | tee $@
	uname -srm | tee -a $@
	if [ -f /proc/cpuinfo ]; then \
	awk -F: '/^model name/{print $$2}' /proc/cpuinfo \
		| sed -e 's/^ *//g; s/ *$$//g' \
		| tee -a $@; \
	fi
	${CCVER} --version | head -n 1 | tee -a $@
	for i in ${BENCH_DIR}/*${EXT}; \
	do \
	    printf "%-20s " `basename $$i` ; \
	    printf "%s %-11s " \
	        `bash -c "time ./$< ${TEST_OPTIONS} $$i > /dev/null" 2>&1` \
		| awk '{print $$1, $$2}' ; \
	done | tee -a $@

${SIZE_FILE}: ${TARGET} ${BENCH_INPUTS}
	date | tee $@
	uname -srm | tee -a $@
	${CCVER} --version | head -n 1 | tee -a $@
	for i in ${BENCH_DIR}/*${EXT}; \
	do \
	    printf "%-20s " `basename $$i` ; \
	    printf "%4d\n" `./$< ${SIZE_OPTIONS} $$i 2> /dev/null | wc -l` ; \
	done | tee -a $@

#------------------------------------------------------------------------------

INCLUDE_DIR ?= ${PREFIX}/include
LIB_DIR     ?= ${PREFIX}/lib
BIN_DIR     ?= ${PREFIX}/bin

.PHONY: install

install: ${LOCAL_LIB} ${LOCAL_SO} ${EXE_FILES}
	mkdir -p ${INCLUDE_DIR}
	mkdir -p ${LIB_DIR}
	mkdir -p ${BIN_DIR}
	cp *.h ${INCLUDE_DIR}
	cp ${LOCAL_LIB} ${LOCAL_SO} ${LIB_DIR}
	cp ${EXE_FILES} ${BIN_DIR}
	ln -fs ${LIBNAME}.so.${VERSION}  ${LIB_DIR}/${LIBNAME}.so.${SO_MAJOR}
	ln -fs ${LIBNAME}.so.${SO_MAJOR} ${LIB_DIR}/${LIBNAME}.so

#------------------------------------------------------------------------------

clean:
	rm -f *~
	rm -f ${DEL_OBJS} ${YYOBJ}
	rm -f *.tab.c lex.yy.c *.lex.c *.tab.h *.output
	rm -f ${DIFF_FILES} ${TST_DIFFS}
	rm -f ${SHELL_DIFFS}

cleanAll distclean: clean ${LOCAL_CLEAN_TARGETS}
	rm -f ${EXE_FILES} ${TST_DRIVERS}
	rm -f ${DEPEND_FILES}
	rm -f ${LOCAL_LIB} lib/${LIBNAME}.so.*
